Skip to content

Commit

Permalink
Component option propagation (Go SDK) (#2709)
Browse files Browse the repository at this point in the history
<!--Thanks for your contribution. See [CONTRIBUTING](CONTRIBUTING.md)
    for Pulumi's contribution guidelines.

    Help us merge your changes more quickly by adding more details such
    as labels, milestones, and reviewers.-->

### Proposed changes
Epic: #2254
Fixes: #2710

This PR standardizes the option propagation logic for the component
resources in the pulumi-kubernetes Go SDK. The general approach is:
1. In the component resource constructor, compute the child options to
be propagated to any children. The child options consist of the
component as parent, and with `version` and `pluginDownloadURL` if
specified.
2. Compute the invoke options by copying the child options.

### Specification
The component resource is responsible for computing sub-options for
invokes and for child resource declarations. This table outlines the
expected behavior for each [resource
option](https://www.pulumi.com/docs/concepts/options/) when presented to
a component resource.

|  | Propagated | Remarks |
|---|---|---|
| `additionalSecretOutputs` | no | "does not apply to component
resources" |
| `aliases` | no | Inherited via parent-child relationship. |
| `customTimeouts` | no | "does not apply to component resources" |
| `deleteBeforeReplace` | no |  |
| `deletedWith` | no |  |
| `dependsOn` | no | The children implicitly wait for the dependency. |
| `ignoreChanges` | no | Nonsensical to apply directly to children (see
[discussion](pulumi/pulumi#8969)). |
| `import` | no |  |
| `parent` | **yes** | The component becomes the parent. |
| `protect` | no | Inherited (see [p/p
bug](pulumi/pulumi#12431)). |
| `provider` | no | Combined into providers map, then inherited via
parent-child relationship. |
| `providers` | no | Inherited. |
| `replaceOnChanges` | no | "does not apply to component resources" |
| `retainOnDelete` | no | "does not apply to component resources" |
| `transformations` | no | Inherited. |
| `version` | **yes** | Influences default provider selection logic
during invokes.<br/>Should propagate when child resource is from the
same provider type. |
| `pluginDownloadURL` | **yes** | Influences default provider selection
logic during invokes.<br/>Should propagate when child resource is from
the same provider type. |

### Testing
A new test case is provided ([test
case](https://github.com/pulumi/pulumi-kubernetes/blob/073b9dc64e32e4f14fa6691e1b11049007ca2db7/tests/sdk/go/go_test.go#L808),
[test
program](https://github.com/pulumi/pulumi-kubernetes/blob/073b9dc64e32e4f14fa6691e1b11049007ca2db7/tests/sdk/go/options/main.go))
that exercises option propagation across the component resources:
-
[kubernetes.helm.sh.v3.Chart](https://www.pulumi.com/registry/packages/kubernetes/api-docs/helm/v3/chart/)
-
[kubernetes.kustomize.Directory](https://www.pulumi.com/registry/packages/kubernetes/api-docs/kustomize/directory/)
-
[kubernetes.yaml.ConfigGroup](https://www.pulumi.com/registry/packages/kubernetes/api-docs/yaml/configgroup/)
-
[kubernetes.yaml.ConfigFile](https://www.pulumi.com/registry/packages/kubernetes/api-docs/yaml/configfile/)

Upgrade testing must be done manually, with an emphasis on avoiding
replacement due to reparenting.

### Related issues (optional)

The Go SDK doesn't allow for options to be mutated via the
"kubernetes-style" transformation function.
#2666

During testing it was observed that the `parent` field is occasionally
missing from `RegisterResource` RPC call.
pulumi/pulumi#14826

Here's some key related PRs for additional context:
- pulumi/pulumi#8796
- #1601
- #1919
- #2005
  • Loading branch information
EronWright authored Jan 3, 2024
1 parent 99bac0d commit c9fa74e
Show file tree
Hide file tree
Showing 28 changed files with 1,221 additions and 110 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Unreleased
- Fix option propagation in component resources (Go SDK) (https://github.com/pulumi/pulumi-kubernetes/pull/2709)

## 4.6.1 (December 14, 2023)
- Fix: Refine URN lookup by using its core type for more accurate resource identification (https://github.com/pulumi/pulumi-kubernetes/issues/2719)
Expand Down
31 changes: 13 additions & 18 deletions provider/pkg/gen/_go-templates/helm/v3/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,26 +244,23 @@ func NewChart(ctx *pulumi.Context,
},
})

var resourceOrInvokeOptions []pulumi.ResourceOrInvokeOption
for _, o := range opts {
if asResOrInv, ok := o.(pulumi.ResourceOrInvokeOption); ok {
resourceOrInvokeOptions = append(resourceOrInvokeOptions, asResOrInv)
}
}
opts = append(opts, aliases)
err := ctx.RegisterComponentResource("kubernetes:helm.sh/v3:Chart", name, chart, opts...)
if err != nil {
return nil, err
}

// Honor the resource name prefix if specified.
releaseName := name
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
releaseName = args.ResourcePrefix + "-" + name
}

resourceOrInvokeOptions = append(resourceOrInvokeOptions, pulumi.Parent(chart))
parseOpts, err := yaml.GetChildOptions(chart, opts)
if err != nil {
return nil, err
}
resources := args.ToChartArgsOutput().ApplyT(func(args chartArgs) (map[string]pulumi.Resource, error) {
return parseChart(ctx, name, args, resourceOrInvokeOptions...)
return parseChart(ctx, releaseName, args, parseOpts...)
})
chart.Resources = resources

Expand All @@ -287,7 +284,7 @@ func NewChart(ctx *pulumi.Context,
return chart, nil
}

func parseChart(ctx *pulumi.Context, name string, args chartArgs, opts ...pulumi.ResourceOrInvokeOption,
func parseChart(ctx *pulumi.Context, releaseName string, args chartArgs, opts ...pulumi.ResourceOption,
) (map[string]pulumi.Resource, error) {
type jsonOptsArgs struct {
chartArgs
Expand All @@ -296,19 +293,17 @@ func parseChart(ctx *pulumi.Context, name string, args chartArgs, opts ...pulumi
}
jsonOpts := jsonOptsArgs{
chartArgs: args,
ReleaseName: name,
ReleaseName: releaseName,
}

b, err := json.Marshal(jsonOpts)
if err != nil {
return nil, err
}

var invokeOpts []pulumi.InvokeOption
var resourceOpts []pulumi.ResourceOption
for _, o := range opts {
invokeOpts = append(invokeOpts, o)
resourceOpts = append(resourceOpts, o)
invokeOpts, err := yaml.GetInvokeOptions(opts)
if err != nil {
return nil, err
}
objs, err := helmTemplate(ctx, string(b), invokeOpts...)
if err != nil {
Expand All @@ -320,7 +315,7 @@ func parseChart(ctx *pulumi.Context, name string, args chartArgs, opts ...pulumi
transformations = yaml.AddSkipAwaitTransformation(transformations)
}

resources, err := yaml.ParseYamlObjects(ctx, objs, transformations, args.ResourcePrefix, resourceOpts...)
resources, err := yaml.ParseYamlObjects(ctx, objs, transformations, args.ResourcePrefix, opts...)
if err != nil {
return nil, err
}
Expand Down
22 changes: 8 additions & 14 deletions provider/pkg/gen/_go-templates/kustomize/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,12 @@ func NewDirectory(ctx *pulumi.Context,
return nil, err
}

// Honor the resource name prefix if specified.
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
parseOpts, err := yaml.GetChildOptions(chart, opts)
if err != nil {
return nil, err
}

parseOpts := append(opts, pulumi.Parent(chart))
resources := args.ToDirectoryArgsOutput().ApplyT(func(args directoryArgs) (map[string]pulumi.Resource, error) {
return parseDirectory(ctx, name, args, parseOpts...)
return parseDirectory(ctx, args, parseOpts...)
})
chart.Resources = resources

Expand All @@ -182,7 +180,7 @@ func NewDirectory(ctx *pulumi.Context,
return chart, nil
}

func parseDirectory(ctx *pulumi.Context, name string, args directoryArgs, opts ...pulumi.ResourceOption,
func parseDirectory(ctx *pulumi.Context, args directoryArgs, opts ...pulumi.ResourceOption,
) (map[string]pulumi.Resource, error) {
invokeArgs := struct {
Directory string `pulumi:"directory"`
Expand All @@ -191,14 +189,10 @@ func parseDirectory(ctx *pulumi.Context, name string, args directoryArgs, opts .
Result []map[string]interface{} `pulumi:"result"`
}

// Find options which are also Invoke options, and prepare them to pass to Invoke functions
var invokeOpts []pulumi.InvokeOption
for _, opt := range opts {
if invokeOpt, ok := opt.(pulumi.InvokeOption); ok {
invokeOpts = append(invokeOpts, invokeOpt)
}
invokeOpts, err := yaml.GetInvokeOptions(opts)
if err != nil {
return nil, err
}

if err := ctx.Invoke("kubernetes:kustomize:directory", &invokeArgs, &ret, invokeOpts...); err != nil {
return nil, errors.Wrap(err, "kustomize invoke failed")
}
Expand Down
14 changes: 6 additions & 8 deletions provider/pkg/gen/_go-templates/yaml/configFile.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,23 +151,21 @@ func NewConfigFile(ctx *pulumi.Context,

// Now provision all child resources by parsing the YAML file.
if args != nil {
// Honor the resource name prefix if specified.
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
}

transformations := args.Transformations
if args.SkipAwait {
transformations = AddSkipAwaitTransformation(transformations)
}

// Parse and decode the YAML files.
parseOpts := append(opts, pulumi.Parent(configFile))
parseOpts, err := GetChildOptions(configFile, opts)
if err != nil {
return nil, err
}
rs, err := parseDecodeYamlFiles(ctx, &ConfigGroupArgs{
Files: []string{args.File},
Transformations: args.Transformations,
Transformations: transformations,
ResourcePrefix: args.ResourcePrefix,
}, true, parseOpts...)
}, false, parseOpts...)
if err != nil {
return nil, err
}
Expand Down
10 changes: 4 additions & 6 deletions provider/pkg/gen/_go-templates/yaml/configGroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,11 @@ func NewConfigGroup(ctx *pulumi.Context,

// Now provision all child resources by parsing the YAML files.
if args != nil {
// Honor the resource name prefix if specified.
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
}

// Parse and decode the YAML files.
parseOpts := append(opts, pulumi.Parent(configGroup))
parseOpts, err := GetChildOptions(configGroup, opts)
if err != nil {
return nil, err
}
rs, err := parseDecodeYamlFiles(ctx, args, true, parseOpts...)
if err != nil {
return nil, err
Expand Down
48 changes: 40 additions & 8 deletions provider/pkg/gen/_go-templates/yaml/yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,42 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func GetChildOptions(parent pulumi.Resource, opts []pulumi.ResourceOption) ([]pulumi.ResourceOption, error) {
snapshot, err := pulumi.NewResourceOptions(opts...)
if err != nil {
return nil, err
}
childOpts := []pulumi.ResourceOption{pulumi.Parent(parent)}
if snapshot.Version != "" {
childOpts = append(childOpts, pulumi.Version(snapshot.Version))
}
if snapshot.PluginDownloadURL != "" {
childOpts = append(childOpts, pulumi.PluginDownloadURL(snapshot.PluginDownloadURL))
}
return childOpts, nil
}

func GetInvokeOptions(opts []pulumi.ResourceOption) ([]pulumi.InvokeOption, error) {
snapshot, err := pulumi.NewResourceOptions(opts...)
if err != nil {
return nil, err
}
var invokeOpts []pulumi.InvokeOption
if snapshot.Parent != nil {
invokeOpts = append(invokeOpts, pulumi.Parent(snapshot.Parent))
}
if snapshot.Provider != nil {
invokeOpts = append(invokeOpts, pulumi.Provider(snapshot.Provider))
}
if snapshot.Version != "" {
invokeOpts = append(invokeOpts, pulumi.Version(snapshot.Version))
}
if snapshot.PluginDownloadURL != "" {
invokeOpts = append(invokeOpts, pulumi.PluginDownloadURL(snapshot.PluginDownloadURL))
}
return invokeOpts, nil
}

func parseDecodeYamlFiles(ctx *pulumi.Context, args *ConfigGroupArgs, glob bool, opts ...pulumi.ResourceOption,
) (map[string]pulumi.Resource, error) {

Expand Down Expand Up @@ -80,15 +116,11 @@ func parseDecodeYamlFiles(ctx *pulumi.Context, args *ConfigGroupArgs, glob bool,
}
}

// Find options which are also Invoke options, and prepare them to pass to Invoke functions
var invokeOpts []pulumi.InvokeOption
for _, opt := range opts {
if invokeOpt, ok := opt.(pulumi.InvokeOption); ok {
invokeOpts = append(invokeOpts, invokeOpt)
}
}

// Next parse all YAML documents into objects.
invokeOpts, err := GetInvokeOptions(opts)
if err != nil {
return nil, err
}
for _, yaml := range yamls {
// Parse the resulting YAML bytes and turn them into raw Kubernetes objects.
dec, err := yamlDecode(ctx, yaml, invokeOpts...)
Expand Down
31 changes: 13 additions & 18 deletions sdk/go/kubernetes/helm/v3/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,26 +244,23 @@ func NewChart(ctx *pulumi.Context,
},
})

var resourceOrInvokeOptions []pulumi.ResourceOrInvokeOption
for _, o := range opts {
if asResOrInv, ok := o.(pulumi.ResourceOrInvokeOption); ok {
resourceOrInvokeOptions = append(resourceOrInvokeOptions, asResOrInv)
}
}
opts = append(opts, aliases)
err := ctx.RegisterComponentResource("kubernetes:helm.sh/v3:Chart", name, chart, opts...)
if err != nil {
return nil, err
}

// Honor the resource name prefix if specified.
releaseName := name
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
releaseName = args.ResourcePrefix + "-" + name
}

resourceOrInvokeOptions = append(resourceOrInvokeOptions, pulumi.Parent(chart))
parseOpts, err := yaml.GetChildOptions(chart, opts)
if err != nil {
return nil, err
}
resources := args.ToChartArgsOutput().ApplyT(func(args chartArgs) (map[string]pulumi.Resource, error) {
return parseChart(ctx, name, args, resourceOrInvokeOptions...)
return parseChart(ctx, releaseName, args, parseOpts...)
})
chart.Resources = resources

Expand All @@ -287,7 +284,7 @@ func NewChart(ctx *pulumi.Context,
return chart, nil
}

func parseChart(ctx *pulumi.Context, name string, args chartArgs, opts ...pulumi.ResourceOrInvokeOption,
func parseChart(ctx *pulumi.Context, releaseName string, args chartArgs, opts ...pulumi.ResourceOption,
) (map[string]pulumi.Resource, error) {
type jsonOptsArgs struct {
chartArgs
Expand All @@ -296,19 +293,17 @@ func parseChart(ctx *pulumi.Context, name string, args chartArgs, opts ...pulumi
}
jsonOpts := jsonOptsArgs{
chartArgs: args,
ReleaseName: name,
ReleaseName: releaseName,
}

b, err := json.Marshal(jsonOpts)
if err != nil {
return nil, err
}

var invokeOpts []pulumi.InvokeOption
var resourceOpts []pulumi.ResourceOption
for _, o := range opts {
invokeOpts = append(invokeOpts, o)
resourceOpts = append(resourceOpts, o)
invokeOpts, err := yaml.GetInvokeOptions(opts)
if err != nil {
return nil, err
}
objs, err := helmTemplate(ctx, string(b), invokeOpts...)
if err != nil {
Expand All @@ -320,7 +315,7 @@ func parseChart(ctx *pulumi.Context, name string, args chartArgs, opts ...pulumi
transformations = yaml.AddSkipAwaitTransformation(transformations)
}

resources, err := yaml.ParseYamlObjects(ctx, objs, transformations, args.ResourcePrefix, resourceOpts...)
resources, err := yaml.ParseYamlObjects(ctx, objs, transformations, args.ResourcePrefix, opts...)
if err != nil {
return nil, err
}
Expand Down
22 changes: 8 additions & 14 deletions sdk/go/kubernetes/kustomize/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,12 @@ func NewDirectory(ctx *pulumi.Context,
return nil, err
}

// Honor the resource name prefix if specified.
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
parseOpts, err := yaml.GetChildOptions(chart, opts)
if err != nil {
return nil, err
}

parseOpts := append(opts, pulumi.Parent(chart))
resources := args.ToDirectoryArgsOutput().ApplyT(func(args directoryArgs) (map[string]pulumi.Resource, error) {
return parseDirectory(ctx, name, args, parseOpts...)
return parseDirectory(ctx, args, parseOpts...)
})
chart.Resources = resources

Expand All @@ -182,7 +180,7 @@ func NewDirectory(ctx *pulumi.Context,
return chart, nil
}

func parseDirectory(ctx *pulumi.Context, name string, args directoryArgs, opts ...pulumi.ResourceOption,
func parseDirectory(ctx *pulumi.Context, args directoryArgs, opts ...pulumi.ResourceOption,
) (map[string]pulumi.Resource, error) {
invokeArgs := struct {
Directory string `pulumi:"directory"`
Expand All @@ -191,14 +189,10 @@ func parseDirectory(ctx *pulumi.Context, name string, args directoryArgs, opts .
Result []map[string]interface{} `pulumi:"result"`
}

// Find options which are also Invoke options, and prepare them to pass to Invoke functions
var invokeOpts []pulumi.InvokeOption
for _, opt := range opts {
if invokeOpt, ok := opt.(pulumi.InvokeOption); ok {
invokeOpts = append(invokeOpts, invokeOpt)
}
invokeOpts, err := yaml.GetInvokeOptions(opts)
if err != nil {
return nil, err
}

if err := ctx.Invoke("kubernetes:kustomize:directory", &invokeArgs, &ret, invokeOpts...); err != nil {
return nil, errors.Wrap(err, "kustomize invoke failed")
}
Expand Down
14 changes: 6 additions & 8 deletions sdk/go/kubernetes/yaml/configFile.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,23 +151,21 @@ func NewConfigFile(ctx *pulumi.Context,

// Now provision all child resources by parsing the YAML file.
if args != nil {
// Honor the resource name prefix if specified.
if args.ResourcePrefix != "" {
name = args.ResourcePrefix + "-" + name
}

transformations := args.Transformations
if args.SkipAwait {
transformations = AddSkipAwaitTransformation(transformations)
}

// Parse and decode the YAML files.
parseOpts := append(opts, pulumi.Parent(configFile))
parseOpts, err := GetChildOptions(configFile, opts)
if err != nil {
return nil, err
}
rs, err := parseDecodeYamlFiles(ctx, &ConfigGroupArgs{
Files: []string{args.File},
Transformations: args.Transformations,
Transformations: transformations,
ResourcePrefix: args.ResourcePrefix,
}, true, parseOpts...)
}, false, parseOpts...)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit c9fa74e

Please sign in to comment.