Compare commits

...

1 commit

2 changed files with 38 additions and 23 deletions

View file

@ -332,7 +332,7 @@ func (ctx *Context) ReadResource(
providers := mergeProviders(t, options.Parent, options.Provider, options.Providers)
// Create resolvers for the resource's outputs.
res := makeResourceState(t, name, resource, providers, aliasURNs, transformations)
res := makePendingResourceState(t, name, resource, providers, aliasURNs, transformations)
// Kick off the resource read operation. This will happen asynchronously and resolve the above properties.
go func() {
@ -409,7 +409,12 @@ func (ctx *Context) ReadResource(
//
func (ctx *Context) RegisterResource(
t, name string, props Input, resource Resource, opts ...ResourceOption) error {
_, err := ctx.registerResource(t, name, props, resource, false /*remote*/, opts...)
return err
}
func (ctx *Context) RegisterResourceWithResult(
t, name string, props Input, resource Resource, opts ...ResourceOption) (*PendingResourceState, error) {
return ctx.registerResource(t, name, props, resource, false /*remote*/, opts...)
}
@ -457,17 +462,17 @@ func (ctx *Context) getResource(urn string) (*pulumirpc.RegisterResourceResponse
}
func (ctx *Context) registerResource(
t, name string, props Input, resource Resource, remote bool, opts ...ResourceOption) error {
t, name string, props Input, resource Resource, remote bool, opts ...ResourceOption) (*PendingResourceState, error) {
if t == "" {
return errors.New("resource type argument cannot be empty")
return nil, errors.New("resource type argument cannot be empty")
} else if name == "" {
return errors.New("resource name argument (for URN creation) cannot be empty")
return nil, errors.New("resource name argument (for URN creation) cannot be empty")
}
_, custom := resource.(CustomResource)
if _, isProvider := resource.(ProviderResource); isProvider && !strings.HasPrefix(t, "pulumi:providers:") {
return errors.New("provider resource type must begin with \"pulumi:providers:\"")
return nil, errors.New("provider resource type must begin with \"pulumi:providers:\"")
}
if props != nil {
@ -477,7 +482,7 @@ func (ctx *Context) registerResource(
}
if !(propsType.Kind() == reflect.Struct ||
(propsType.Kind() == reflect.Map && propsType.Key().Kind() == reflect.String)) {
return errors.New("props must be a struct or map or a pointer to a struct or map")
return nil, errors.New("props must be a struct or map or a pointer to a struct or map")
}
}
@ -490,25 +495,25 @@ func (ctx *Context) registerResource(
// user-provided properties and options assigned to this resource.
props, options, transformations, err := applyTransformations(t, name, props, resource, opts, options)
if err != nil {
return err
return nil, err
}
// Collapse aliases to URNs.
aliasURNs, err := ctx.collapseAliases(options.Aliases, t, name, options.Parent)
if err != nil {
return err
return nil, err
}
// Note that we're about to make an outstanding RPC request, so that we can rendezvous during shutdown.
if err := ctx.beginRPC(); err != nil {
return err
return nil, err
}
// Merge providers.
providers := mergeProviders(t, options.Parent, options.Provider, options.Providers)
// Create resolvers for the resource's outputs.
resState := makeResourceState(t, name, resource, providers, aliasURNs, transformations)
resState := makePendingResourceState(t, name, resource, providers, aliasURNs, transformations)
// Kick off the resource registration. If we are actually performing a deployment, the resulting properties
// will be resolved asynchronously as the RPC operation completes. If we're just planning, values won't resolve.
@ -580,7 +585,7 @@ func (ctx *Context) registerResource(
}
}()
return nil
return resState, nil
}
func (ctx *Context) RegisterComponentResource(
@ -591,12 +596,12 @@ func (ctx *Context) RegisterComponentResource(
func (ctx *Context) RegisterRemoteComponentResource(
t, name string, props Input, resource ComponentResource, opts ...ResourceOption) error {
return ctx.registerResource(t, name, props, resource, true /*remote*/, opts...)
_, err := ctx.registerResource(t, name, props, resource, true /*remote*/, opts...)
return err
}
// resourceState contains the results of a resource registration operation.
type resourceState struct {
// PendingResourceState contains the results of a resource registration operation.
type PendingResourceState struct {
outputs map[string]Output
providers map[string]ProviderResource
aliases []URNOutput
@ -604,6 +609,16 @@ type resourceState struct {
transformations []ResourceTransformation
}
// GetOutput returns an output by the given name, allocating storage for it lazily if needed.
func (st *PendingResourceState) GetOutput(k string) Output {
out, ok := st.outputs[k]
if !ok {
out, _, _ = NewOutput()
st.outputs[k] = out
}
return out
}
// Apply transformations and return the transformations themselves, as well as the transformed props and opts.
func applyTransformations(t, name string, props Input, resource Resource, opts []ResourceOption,
options *resourceOptions) (Input, *resourceOptions, []ResourceTransformation, error) {
@ -697,17 +712,17 @@ func (ctx *Context) collapseAliases(aliases []Alias, t, name string, parent Reso
var mapOutputType = reflect.TypeOf((*MapOutput)(nil)).Elem()
// makeResourceState creates a set of resolvers that we'll use to finalize state, for URNs, IDs, and output
// makePendingResourceState creates a set of resolvers that we'll use to finalize state, for URNs, IDs, and output
// properties.
func makeResourceState(t, name string, resourceV Resource, providers map[string]ProviderResource,
aliases []URNOutput, transformations []ResourceTransformation) *resourceState {
func makePendingResourceState(t, name string, resourceV Resource, providers map[string]ProviderResource,
aliases []URNOutput, transformations []ResourceTransformation) *PendingResourceState {
// Ensure that the input resource is a pointer to a struct. Note that we don't fail if it is not, and we probably
// ought to.
resource := reflect.ValueOf(resourceV)
typ := resource.Type()
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
return &resourceState{}
return &PendingResourceState{}
}
resource, typ = resource.Elem(), typ.Elem()
@ -730,7 +745,7 @@ func makeResourceState(t, name string, resourceV Resource, providers map[string]
// former is used for any URN or ID fields; the latter are used to determine the expected outputs of the resource
// after its RegisterResource call completes. For each of those fields, create an appropriately-typed Output and
// map the Output to its property name so we can resolve it later.
state := &resourceState{outputs: map[string]Output{}}
state := &PendingResourceState{outputs: map[string]Output{}}
for i := 0; i < typ.NumField(); i++ {
fieldV := resource.Field(i)
if !fieldV.CanSet() {
@ -793,7 +808,7 @@ func makeResourceState(t, name string, resourceV Resource, providers map[string]
}
// resolve resolves the resource outputs using the given error and/or values.
func (state *resourceState) resolve(dryrun bool, err error, inputs *resourceInputs, urn, id string,
func (state *PendingResourceState) resolve(dryrun bool, err error, inputs *resourceInputs, urn, id string,
result *structpb.Struct, deps map[string][]Resource) {
var inprops resource.PropertyMap
@ -883,7 +898,7 @@ type resourceInputs struct {
// prepareResourceInputs prepares the inputs for a resource operation, shared between read and register.
func (ctx *Context) prepareResourceInputs(props Input, t string,
opts *resourceOptions, resource *resourceState) (*resourceInputs, error) {
opts *resourceOptions, resource *PendingResourceState) (*resourceInputs, error) {
providers := resource.providers

View file

@ -264,7 +264,7 @@ type testResource struct {
func TestResourceState(t *testing.T) {
var theResource testResource
state := makeResourceState("", "", &theResource, nil, nil, nil)
state := makePendingResourceState("", "", &theResource, nil, nil, nil)
resolved, _, _, _ := marshalInputs(&testResourceInputs{
Any: String("foo"),