Skip to content

Commit

Permalink
Allow override a Pulumi token by using SetToken of infer.Annotator (#129
Browse files Browse the repository at this point in the history
)

This is a cleaned up version of
#128 (written by
@tmeckel).

---------

Co-authored-by: Thomas Meckel <tmeckel@users.noreply.github.com>
  • Loading branch information
iwahbe and tmeckel authored Sep 29, 2023
1 parent 6401f30 commit fbb4f93
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 30 deletions.
3 changes: 1 addition & 2 deletions infer/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ func (rc *derivedComponentController[R, I, O]) GetSchema(reg schema.RegisterDeri
}

func (rc *derivedComponentController[R, I, O]) GetToken() (tokens.Type, error) {
var r R
return introspect.GetToken("pkg", r)
return getToken[R](nil)
}

func (rc *derivedComponentController[R, I, O]) Construct(
Expand Down
16 changes: 9 additions & 7 deletions infer/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (

p "github.com/pulumi/pulumi-go-provider"
"github.com/pulumi/pulumi-go-provider/infer/internal/ende"
"github.com/pulumi/pulumi-go-provider/internal/introspect"
t "github.com/pulumi/pulumi-go-provider/middleware"
"github.com/pulumi/pulumi-go-provider/middleware/schema"
)
Expand Down Expand Up @@ -54,12 +53,15 @@ type derivedInvokeController[F Fn[I, O], I, O any] struct{}
func (derivedInvokeController[F, I, O]) isInferredFunction() {}

func (*derivedInvokeController[F, I, O]) GetToken() (tokens.Type, error) {
var f F
tk, err := introspect.GetToken("pkg", f)
if err != nil {
return "", err
}
return fnToken(tk), nil
// By default, we get resource style tokens:
//
// pkg:index:FizzBuzz
//
// Functions use a different capitalization convention, so we need to convert:
//
// pkg:index:fizzBuzz
//
return getToken[F](fnToken)
}

func fnToken(tk tokens.Type) tokens.Type {
Expand Down
38 changes: 36 additions & 2 deletions infer/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package infer

import (
"fmt"
"reflect"

"github.com/hashicorp/go-multierror"
pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
Expand Down Expand Up @@ -138,6 +139,21 @@ type Annotator interface {
// Annotate a struct field with a default value. The default value must be a primitive
// type in the pulumi type system.
SetDefault(i any, defaultValue any, env ...string)

// Set the token of the annotated type.
//
// module and name should be valid Pulumi token segments. The package name will be
// inferred from the provider.
//
// For example:
//
// a.SetToken("mymodule", "MyResource")
//
// On a provider created with the name "mypkg" will have the token:
//
// mypkg:mymodule:MyResource
//
SetToken(module, name string)
}

// Annotated is used to describe the fields of an object or a resource. Annotated can be
Expand Down Expand Up @@ -680,9 +696,27 @@ func (*derivedResourceController[R, I, O]) GetSchema(reg schema.RegisterDerivati
return r, errs.ErrorOrNil()
}

func (*derivedResourceController[R, I, O]) GetToken() (tokens.Type, error) {
func getToken[R any](transform func(tokens.Type) tokens.Type) (tokens.Type, error) {
var r R
return introspect.GetToken("pkg", r)
return getTokenOf(reflect.TypeOf(r), transform)
}

func getTokenOf(t reflect.Type, transform func(tokens.Type) tokens.Type) (tokens.Type, error) {
annotator := getAnnotated(t)
if annotator.Token != "" {
return tokens.Type(annotator.Token), nil
}

tk, err := introspect.GetToken("pkg", t)
if transform == nil || err != nil {
return tk, err
}

return transform(tk), nil
}

func (*derivedResourceController[R, I, O]) GetToken() (tokens.Type, error) {
return getToken[R](nil)
}

func (*derivedResourceController[R, I, O]) getInstance() *R {
Expand Down
5 changes: 4 additions & 1 deletion infer/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func getAnnotated(t reflect.Type) introspect.Annotator {
for k, v := range src.DefaultEnvs {
(*dst).DefaultEnvs[k] = v
}
dst.Token = src.Token
}

ret := introspect.Annotator{
Expand Down Expand Up @@ -333,10 +334,12 @@ func structReferenceToken(t reflect.Type, extTag *introspect.ExplicitType) (sche
t.Implements(reflect.TypeOf(new(pulumi.Output)).Elem()) {
return schema.TypeSpec{}, false, nil
}
tk, err := introspect.GetToken("pkg", reflect.New(t).Elem().Interface())

tk, err := getTokenOf(t, nil)
if err != nil {
return schema.TypeSpec{}, true, err
}

return schema.TypeSpec{
Ref: "#/types/" + tk.String(),
}, true, nil
Expand Down
198 changes: 198 additions & 0 deletions infer/tests/token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2023, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tests

import (
"testing"

"github.com/blang/semver"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

p "github.com/pulumi/pulumi-go-provider"
"github.com/pulumi/pulumi-go-provider/infer"
"github.com/pulumi/pulumi-go-provider/integration"
)

type CustomToken struct{}

func (c *CustomToken) Annotate(a infer.Annotator) { a.SetToken("overwritten", "Tk") }

func (*CustomToken) Create(
ctx p.Context, name string, inputs TokenArgs, preview bool,
) (string, TokenResult, error) {
panic("unimplemented")
}

type TokenArgs struct {
Array []ObjectToken `pulumi:"arr"`

Single ObjectToken `pulumi:"single"`
}
type TokenResult struct {
Map map[string]ObjectToken `pulumi:"m"`
}

type TokenComponent struct{ pulumi.ResourceState }

type ComponentToken struct{}

// Check that we allow other capitalization schemes
func (c *ComponentToken) Annotate(a infer.Annotator) { a.SetToken("cmp", "tK") }

func (*ComponentToken) Construct(
ctx *pulumi.Context, name, typ string, inputs TokenArgs, opts pulumi.ResourceOption,
) (*TokenComponent, error) {
panic("unimplemented")
}

type FnToken struct{}

func (c *FnToken) Annotate(a infer.Annotator) { a.SetToken("fn", "TK") }

func (*FnToken) Call(ctx p.Context, input TokenArgs) (output TokenResult, err error) {
panic("unimplemented")
}

type ObjectToken struct {
Value string `pulumi:"value"`
}

func (c *ObjectToken) Annotate(a infer.Annotator) { a.SetToken("obj", "Customized") }

func TestTokens(t *testing.T) {
provider := infer.Provider(infer.Options{
Resources: []infer.InferredResource{
infer.Resource[*CustomToken, TokenArgs, TokenResult](),
},
Components: []infer.InferredComponent{
infer.Component[*ComponentToken, TokenArgs, *TokenComponent](),
},
Functions: []infer.InferredFunction{
infer.Function[*FnToken, TokenArgs, TokenResult](),
},
ModuleMap: map[tokens.ModuleName]tokens.ModuleName{"overwritten": "index"},
})
server := integration.NewServer("test", semver.MustParse("1.0.0"), provider)

schema, err := server.GetSchema(p.GetSchemaRequest{})
require.NoError(t, err)

assert.JSONEq(t, `{
"name": "test",
"version": "1.0.0",
"config": {},
"types": {
"test:obj:Customized": {
"properties": {
"value": {
"type": "string"
}
},
"type": "object",
"required": [
"value"
]
}
},
"provider": {},
"resources": {
"test:cmp:tK": {
"inputProperties": {
"arr": {
"type": "array",
"items": {
"$ref": "#/types/test:obj:Customized"
}
},
"single": {
"$ref": "#/types/test:obj:Customized"
}
},
"requiredInputs": [
"arr",
"single"
],
"isComponent": true
},
"test:index:Tk": {
"properties": {
"m": {
"type": "object",
"additionalProperties": {
"$ref": "#/types/test:obj:Customized"
}
}
},
"required": [
"m"
],
"inputProperties": {
"arr": {
"type": "array",
"items": {
"$ref": "#/types/test:obj:Customized"
}
},
"single": {
"$ref": "#/types/test:obj:Customized"
}
},
"requiredInputs": [
"arr",
"single"
]
}
},
"functions": {
"test:fn:TK": {
"inputs": {
"properties": {
"arr": {
"type": "array",
"items": {
"$ref": "#/types/test:obj:Customized"
}
},
"single": {
"$ref": "#/types/test:obj:Customized"
}
},
"type": "object",
"required": [
"arr",
"single"
]
},
"outputs": {
"properties": {
"m": {
"type": "object",
"additionalProperties": {
"$ref": "#/types/test:obj:Customized"
}
}
},
"type": "object",
"required": [
"m"
]
}
}
}
}`, schema.Schema)
}
8 changes: 6 additions & 2 deletions infer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ func isEnum(t reflect.Type) (enum, bool) {
Name: v.FieldByName("Name").String(),
}
}
tk, err := introspect.GetToken("pkg", reflect.New(t).Elem().Interface())

tk, err := getTokenOf(t, nil)
contract.AssertNoErrorf(err, "failed to get token for enum: %s", t)

return enum{
token: tk.String(),
values: values,
Expand Down Expand Up @@ -244,10 +246,12 @@ func registerTypes[T any](reg schema.RegisterDerivativeType) error {
if err != nil {
return false, err
}
tk, err := introspect.GetToken("pkg", reflect.New(t).Interface())

tk, err := getTokenOf(t, nil)
if err != nil {
return false, err
}

return reg(tk, pschema.ComplexTypeSpec{ObjectTypeSpec: *spec}), nil
}
return true, nil
Expand Down
13 changes: 13 additions & 0 deletions internal/introspect/annotator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package introspect
import (
"fmt"
"reflect"

"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
)

func NewAnnotator(resource any) Annotator {
Expand All @@ -33,6 +35,7 @@ type Annotator struct {
Descriptions map[string]string
Defaults map[string]any
DefaultEnvs map[string][]string
Token string

matcher FieldMatcher
}
Expand Down Expand Up @@ -83,3 +86,13 @@ func (a *Annotator) SetDefault(i any, defaultValue any, env ...string) {
a.Defaults[field.Name] = defaultValue
a.DefaultEnvs[field.Name] = append(a.DefaultEnvs[field.Name], env...)
}

func (a *Annotator) SetToken(module, token string) {
if !tokens.IsQName(module) {
panic(fmt.Sprintf("Module (%q) must comply with %s, but does not", module, tokens.QNameRegexp))
}
if !tokens.IsName(token) {
panic(fmt.Sprintf("Token (%q) must comply with %s, but does not", token, tokens.NameRegexp))
}
a.Token = fmt.Sprintf("pkg:%s:%s", module, token)
}
Loading

0 comments on commit fbb4f93

Please sign in to comment.