Skip to content

Commit 824fb39

Browse files
authoredJul 1, 2021
[sdk/go] Support for implementing methods in provider (pulumi#7379)
1 parent eb32039 commit 824fb39

File tree

9 files changed

+520
-0
lines changed

9 files changed

+520
-0
lines changed
 

‎CHANGELOG_PENDING.md

+3
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44
- [sdk] - Add `replaceOnChanges` resource option.
55
[#7226](https://github.com/pulumi/pulumi/pull/722
66

7+
- [sdk/go] - Support for authoring resource methods in Go
8+
[#7379](https://github.com/pulumi/pulumi/pull/7379)
9+
710
### Bug Fixes

‎Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ test_build:: $(SUB_PROJECTS:%=%_install)
6565
cd tests/integration/component_provider_schema/testcomponent-go && go build -o pulumi-resource-testcomponent
6666
cd tests/integration/construct_component_error_apply/testcomponent && yarn install && yarn link @pulumi/pulumi && yarn run tsc
6767
cd tests/integration/construct_component_methods/testcomponent && yarn install && yarn link @pulumi/pulumi && yarn run tsc
68+
cd tests/integration/construct_component_methods/testcomponent-go && go build -o pulumi-resource-testcomponent
6869

6970
test_all:: build test_build $(SUB_PROJECTS:%=%_install)
7071
cd pkg && $(GO_TEST) ${PROJECT_PKGS}

‎build.proj

+1
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@
307307
<Exec Command="go build -o pulumi-resource-testcomponent.exe" WorkingDirectory="$(TestsDirectory)\integration\construct_component_unknown\testcomponent-go" />
308308
<Exec Command="yarn run tsc" WorkingDirectory="$(TestsDirectory)\integration\construct_component_error_apply\testcomponent" />
309309
<Exec Command="yarn run tsc" WorkingDirectory="$(TestsDirectory)\integration\construct_component_methods\testcomponent" />
310+
<Exec Command="go build -o pulumi-resource-testcomponent.exe" WorkingDirectory="$(TestsDirectory)\integration\construct_component_methods\testcomponent-go" />
310311

311312
<!-- Install pulumi SDK into the venv managed by pipenv. -->
312313
<Exec Command="pipenv run pip install -e ."

‎sdk/go/pulumi/provider.go

+172
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,175 @@ func newConstructResult(resource ComponentResource) (URNInput, Input, error) {
342342

343343
return resource.URN(), state, nil
344344
}
345+
346+
type callFunc func(ctx *Context, tok string, args map[string]interface{}) (Input, error)
347+
348+
// call adapts the gRPC CallRequest/CallResponse to/from the Pulumi Go SDK programming model.
349+
func call(ctx context.Context, req *pulumirpc.CallRequest, engineConn *grpc.ClientConn,
350+
callF callFunc) (*pulumirpc.CallResponse, error) {
351+
352+
// Configure the RunInfo.
353+
runInfo := RunInfo{
354+
Project: req.GetProject(),
355+
Stack: req.GetStack(),
356+
Config: req.GetConfig(),
357+
Parallel: int(req.GetParallel()),
358+
DryRun: req.GetDryRun(),
359+
MonitorAddr: req.GetMonitorEndpoint(),
360+
engineConn: engineConn,
361+
}
362+
pulumiCtx, err := NewContext(ctx, runInfo)
363+
if err != nil {
364+
return nil, errors.Wrap(err, "constructing run context")
365+
}
366+
367+
// Deserialize the inputs and apply appropriate dependencies.
368+
argDependencies := req.GetArgDependencies()
369+
deserializedArgs, err := plugin.UnmarshalProperties(
370+
req.GetArgs(),
371+
plugin.MarshalOptions{KeepSecrets: true, KeepResources: true, KeepUnknowns: req.GetDryRun()},
372+
)
373+
if err != nil {
374+
return nil, errors.Wrap(err, "unmarshaling inputs")
375+
}
376+
args := make(map[string]interface{}, len(deserializedArgs))
377+
for key, value := range deserializedArgs {
378+
k := string(key)
379+
var deps []Resource
380+
if inputDeps, ok := argDependencies[k]; ok {
381+
deps = make([]Resource, len(inputDeps.GetUrns()))
382+
for i, depURN := range inputDeps.GetUrns() {
383+
deps[i] = pulumiCtx.newDependencyResource(URN(depURN))
384+
}
385+
}
386+
387+
args[k] = &constructInput{
388+
value: value,
389+
deps: deps,
390+
}
391+
}
392+
393+
result, err := callF(pulumiCtx, req.GetTok(), args)
394+
if err != nil {
395+
return nil, err
396+
}
397+
398+
// Wait for async work to finish.
399+
if err = pulumiCtx.wait(); err != nil {
400+
return nil, err
401+
}
402+
403+
// Serialize all result properties, first by awaiting them, and then marshaling them to the requisite gRPC values.
404+
resolvedProps, propertyDeps, _, err := marshalInputs(result)
405+
if err != nil {
406+
return nil, errors.Wrap(err, "marshaling properties")
407+
}
408+
409+
// Marshal all properties for the RPC call.
410+
keepUnknowns := req.GetDryRun()
411+
rpcProps, err := plugin.MarshalProperties(
412+
resolvedProps,
413+
plugin.MarshalOptions{KeepSecrets: true, KeepUnknowns: keepUnknowns, KeepResources: pulumiCtx.keepResources})
414+
if err != nil {
415+
return nil, errors.Wrap(err, "marshaling properties")
416+
}
417+
418+
// Convert the property dependencies map for RPC and remove duplicates.
419+
rpcPropertyDeps := make(map[string]*pulumirpc.CallResponse_ReturnDependencies)
420+
for k, deps := range propertyDeps {
421+
sort.Slice(deps, func(i, j int) bool { return deps[i] < deps[j] })
422+
423+
urns := make([]string, 0, len(deps))
424+
for i, d := range deps {
425+
if i > 0 && urns[i-1] == string(d) {
426+
continue
427+
}
428+
urns = append(urns, string(d))
429+
}
430+
431+
rpcPropertyDeps[k] = &pulumirpc.CallResponse_ReturnDependencies{
432+
Urns: urns,
433+
}
434+
}
435+
436+
return &pulumirpc.CallResponse{
437+
Return: rpcProps,
438+
ReturnDependencies: rpcPropertyDeps,
439+
}, nil
440+
}
441+
442+
// callArgsCopyTo sets the args on the given args struct. If there is a `__self__` argument, it will be
443+
// returned, otherwise it will return nil.
444+
func callArgsCopyTo(ctx *Context, source map[string]interface{}, args interface{}) (Resource, error) {
445+
// Use the same implementation as construct.
446+
if err := constructInputsCopyTo(ctx, source, args); err != nil {
447+
return nil, err
448+
}
449+
450+
// Retrieve the `__self__` arg.
451+
self, err := callArgsSelf(ctx, source)
452+
if err != nil {
453+
return nil, err
454+
}
455+
456+
return self, nil
457+
}
458+
459+
// callArgsSelf retrieves the `__self__` argument. If `__self__` is present the value is returned,
460+
// otherwise the returned value will be nil.
461+
func callArgsSelf(ctx *Context, source map[string]interface{}) (Resource, error) {
462+
v, ok := source["__self__"]
463+
if !ok {
464+
return nil, nil
465+
}
466+
467+
ci := v.(*constructInput)
468+
if ci.value.ContainsUnknowns() {
469+
return nil, errors.New("__self__ is unknown")
470+
}
471+
472+
value, secret, err := unmarshalPropertyValue(ctx, ci.value)
473+
if err != nil {
474+
return nil, errors.Wrap(err, "unmarshaling __self__")
475+
}
476+
if secret {
477+
return nil, errors.New("__self__ is a secret")
478+
}
479+
480+
return value.(Resource), nil
481+
}
482+
483+
// newCallResult converts a result struct into an input Map that can be marshalled.
484+
func newCallResult(result interface{}) (Input, error) {
485+
if result == nil {
486+
return nil, errors.New("result must not be nil")
487+
}
488+
489+
resultV := reflect.ValueOf(result)
490+
typ := resultV.Type()
491+
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
492+
return nil, errors.New("result must be a pointer to a struct")
493+
}
494+
resultV, typ = resultV.Elem(), typ.Elem()
495+
496+
ret := make(Map)
497+
for i := 0; i < typ.NumField(); i++ {
498+
fieldV := resultV.Field(i)
499+
if !fieldV.CanInterface() {
500+
continue
501+
}
502+
field := typ.Field(i)
503+
tag, has := field.Tag.Lookup("pulumi")
504+
if !has {
505+
continue
506+
}
507+
val := fieldV.Interface()
508+
if v, ok := val.(Input); ok {
509+
ret[tag] = v
510+
} else {
511+
ret[tag] = ToOutput(val)
512+
}
513+
}
514+
515+
return ret, nil
516+
}

‎sdk/go/pulumi/provider/provider.go

+72
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,62 @@ func NewConstructResult(resource pulumi.ComponentResource) (*ConstructResult, er
7777
}, nil
7878
}
7979

80+
type CallFunc func(ctx *pulumi.Context, tok string, args CallArgs) (*CallResult, error)
81+
82+
// Call adapts the gRPC CallRequest/CallResponse to/from the Pulumi Go SDK programming model.
83+
func Call(ctx context.Context, req *pulumirpc.CallRequest, engineConn *grpc.ClientConn,
84+
call CallFunc) (*pulumirpc.CallResponse, error) {
85+
return linkedCall(ctx, req, engineConn, func(pulumiCtx *pulumi.Context, tok string,
86+
args map[string]interface{}) (pulumi.Input, error) {
87+
ca := CallArgs{ctx: pulumiCtx, args: args}
88+
result, err := call(pulumiCtx, tok, ca)
89+
if err != nil {
90+
return nil, err
91+
}
92+
return result.Return, nil
93+
})
94+
}
95+
96+
// CallArgs represents the Call's arguments.
97+
type CallArgs struct {
98+
ctx *pulumi.Context
99+
args map[string]interface{}
100+
}
101+
102+
// Map returns the args as a Map.
103+
func (a CallArgs) Map() (pulumi.Map, error) {
104+
// Use the same implementation as construct.
105+
return linkedConstructInputsMap(a.ctx, a.args)
106+
}
107+
108+
// CopyTo sets the args on the given args struct. If there is a `__self__` argument, it will be
109+
// returned, otherwise it will return nil.
110+
func (a CallArgs) CopyTo(args interface{}) (pulumi.Resource, error) {
111+
return linkedCallArgsCopyTo(a.ctx, a.args, args)
112+
}
113+
114+
// Self retrieves the `__self__` argument. If `__self__` is present the value is returned,
115+
// otherwise the returned value will be nil.
116+
func (a CallArgs) Self() (pulumi.Resource, error) {
117+
return linkedCallArgsSelf(a.ctx, a.args)
118+
}
119+
120+
// CallResult is the result of the Call.
121+
type CallResult struct {
122+
Return pulumi.Input
123+
}
124+
125+
// NewCallResult creates a CallResult from the given result.
126+
func NewCallResult(result interface{}) (*CallResult, error) {
127+
ret, err := linkedNewCallResult(result)
128+
if err != nil {
129+
return nil, err
130+
}
131+
return &CallResult{
132+
Return: ret,
133+
}, nil
134+
}
135+
80136
type constructFunc func(ctx *pulumi.Context, typ, name string, inputs map[string]interface{},
81137
options pulumi.ResourceOption) (pulumi.URNInput, pulumi.Input, error)
82138

@@ -92,3 +148,19 @@ func linkedConstructInputsCopyTo(ctx *pulumi.Context, inputs map[string]interfac
92148

93149
// linkedNewConstructResult is made available here from ../provider_linked.go via go:linkname.
94150
func linkedNewConstructResult(resource pulumi.ComponentResource) (pulumi.URNInput, pulumi.Input, error)
151+
152+
type callFunc func(ctx *pulumi.Context, tok string, args map[string]interface{}) (pulumi.Input, error)
153+
154+
// linkedCall is made available here from ../provider_linked.go via go:linkname.
155+
func linkedCall(ctx context.Context, req *pulumirpc.CallRequest, engineConn *grpc.ClientConn,
156+
callF callFunc) (*pulumirpc.CallResponse, error)
157+
158+
// linkedCallArgsCopyTo is made available here from ../provider_linked.go via go:linkname.
159+
func linkedCallArgsCopyTo(ctx *pulumi.Context, source map[string]interface{},
160+
args interface{}) (pulumi.Resource, error)
161+
162+
// linkedCallArgsSelf is made available here from ../provider_linked.go via go:linkname.
163+
func linkedCallArgsSelf(ctx *pulumi.Context, source map[string]interface{}) (pulumi.Resource, error)
164+
165+
// linkedNewCallResult is made available here from ../provider_linked.go via go:linkname.
166+
func linkedNewCallResult(result interface{}) (pulumi.Input, error)

‎sdk/go/pulumi/provider_linked.go

+21
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,24 @@ func linkedConstructInputsCopyTo(ctx *Context, inputs map[string]interface{}, ar
4848
func linkedNewConstructResult(resource ComponentResource) (URNInput, Input, error) {
4949
return newConstructResult(resource)
5050
}
51+
52+
//go:linkname linkedCall github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedCall
53+
func linkedCall(ctx context.Context, req *pulumirpc.CallRequest, engineConn *grpc.ClientConn,
54+
callF callFunc) (*pulumirpc.CallResponse, error) {
55+
return call(ctx, req, engineConn, callF)
56+
}
57+
58+
//go:linkname linkedCallArgsCopyTo github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedCallArgsCopyTo
59+
func linkedCallArgsCopyTo(ctx *Context, source map[string]interface{}, args interface{}) (Resource, error) {
60+
return callArgsCopyTo(ctx, source, args)
61+
}
62+
63+
//go:linkname linkedCallArgsSelf github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedCallArgsSelf
64+
func linkedCallArgsSelf(ctx *Context, source map[string]interface{}) (Resource, error) {
65+
return callArgsSelf(ctx, source)
66+
}
67+
68+
//go:linkname linkedNewCallResult github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider.linkedNewCallResult
69+
func linkedNewCallResult(result interface{}) (Input, error) {
70+
return newCallResult(result)
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pulumi-resource-testcomponent
2+
pulumi-resource-testcomponent.exe

0 commit comments

Comments
 (0)