diff --git a/deploy/crds/pulumi.com_stacks.yaml b/deploy/crds/pulumi.com_stacks.yaml index b9d5a456..5bf6c1c6 100644 --- a/deploy/crds/pulumi.com_stacks.yaml +++ b/deploy/crds/pulumi.com_stacks.yaml @@ -80,6 +80,83 @@ spec: which can be optionally specified inline. If this is omitted, configuration is assumed to be checked in and taken from the source repository. type: object + configRefs: + description: (optional) ConfigRefs is an optional list of configuration + values for this stack, which can be specified through each ConfigRef. + If this is omitted, configuration is assumed to be checked in and + taken from the source repository. If present, ConfigRefs values + will be merged with the ones passed through Config field (ConfigRefs + values have precedence, and configs will the same key name will + be overwritten). + items: + additionalProperties: + description: ConfigRef identifies a resource from which config + information can be loaded. Environment variables, files on the + filesystem, Kubernetes Secrets, ConfigMaps, structured and config + literal values strings are currently supported. + properties: + configmap: + description: ConfigMap refers to a Kubernetes ConfigMap. It + will be assumed the ConfigMap key content is the stack config + in YAML format. + properties: + key: + description: Key within the ConfigMap to use. + type: string + name: + description: Name of the ConfigMap + type: string + namespace: + description: Namespace where the ConfigMap is stored. + Deprecated; non-empty values will be considered invalid + unless namespace isolation is disabled in the controller. + type: string + required: + - key + - name + type: object + env: + description: Env selects an environment variable set on the + operator process + properties: + name: + description: Name of the environment variable + type: string + required: + - name + type: object + filesystem: + description: FileSystem selects a file on the operator's file + system + properties: + path: + description: Path on the filesystem to use to load information + from. + type: string + required: + - path + type: object + literal: + description: Literal refers to a literal config value. It + could be both a single or a structured (in YAML format) + ones. + properties: + value: + description: Value to load + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + type: + description: 'SelectorType is required and signifies the type + of selector. Must be one of: Env, FS, Secret, ConfigMap, + Structured, Literal' + type: string + required: + - type + type: object + type: object + type: array continueResyncOnCommitMatch: description: (optional) ContinueResyncOnCommitMatch - when true - informs the operator to continue trying to update stacks even if @@ -898,6 +975,83 @@ spec: which can be optionally specified inline. If this is omitted, configuration is assumed to be checked in and taken from the source repository. type: object + configRefs: + description: (optional) ConfigRefs is an optional list of configuration + values for this stack, which can be specified through each ConfigRef. + If this is omitted, configuration is assumed to be checked in and + taken from the source repository. If present, ConfigRefs values + will be merged with the ones passed through Config field (ConfigRefs + values have precedence, and configs will the same key name will + be overwritten). + items: + additionalProperties: + description: ConfigRef identifies a resource from which config + information can be loaded. Environment variables, files on the + filesystem, Kubernetes Secrets, ConfigMaps, structured and config + literal values strings are currently supported. + properties: + configmap: + description: ConfigMap refers to a Kubernetes ConfigMap. It + will be assumed the ConfigMap key content is the stack config + in YAML format. + properties: + key: + description: Key within the ConfigMap to use. + type: string + name: + description: Name of the ConfigMap + type: string + namespace: + description: Namespace where the ConfigMap is stored. + Deprecated; non-empty values will be considered invalid + unless namespace isolation is disabled in the controller. + type: string + required: + - key + - name + type: object + env: + description: Env selects an environment variable set on the + operator process + properties: + name: + description: Name of the environment variable + type: string + required: + - name + type: object + filesystem: + description: FileSystem selects a file on the operator's file + system + properties: + path: + description: Path on the filesystem to use to load information + from. + type: string + required: + - path + type: object + literal: + description: Literal refers to a literal config value. It + could be both a single or a structured (in YAML format) + ones. + properties: + value: + description: Value to load + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + type: + description: 'SelectorType is required and signifies the type + of selector. Must be one of: Env, FS, Secret, ConfigMap, + Structured, Literal' + type: string + required: + - type + type: object + type: object + type: array continueResyncOnCommitMatch: description: (optional) ContinueResyncOnCommitMatch - when true - informs the operator to continue trying to update stacks even if diff --git a/docs/stacks.md b/docs/stacks.md index 7a3b28ae..6a1355d9 100644 --- a/docs/stacks.md +++ b/docs/stacks.md @@ -126,6 +126,13 @@ StackSpec defines the desired state of Pulumi Stack being managed by this operat (optional) Config is the configuration for this stack, which can be optionally specified inline. If this is omitted, configuration is assumed to be checked in and taken from the source repository.
false + + configRefs + []map[string]object + + (optional) ConfigRefs is an optional list of configuration values for this stack, which can be specified through each ConfigRef. If this is omitted, configuration is assumed to be checked in and taken from the source repository. If present, ConfigRefs values will be merged with the ones passed through Config field (ConfigRefs values have precedence, and configs will the same key name will be overwritten).
+ + false continueResyncOnCommitMatch boolean @@ -279,6 +286,183 @@ StackSpec defines the desired state of Pulumi Stack being managed by this operat +### Stack.spec.configRefs[index][key] +[↩ Parent](#stackspec) + + + +ConfigRef identifies a resource from which config information can be loaded. Environment variables, files on the filesystem, Kubernetes Secrets, ConfigMaps, structured and config literal values strings are currently supported. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
typestring + SelectorType is required and signifies the type of selector. Must be one of: Env, FS, Secret, ConfigMap, Structured, Literal
+
true
configmapobject + ConfigMap refers to a Kubernetes ConfigMap. It will be assumed the ConfigMap key content is the stack config in YAML format.
+
false
envobject + Env selects an environment variable set on the operator process
+
false
filesystemobject + FileSystem selects a file on the operator's file system
+
false
literalobject + Literal refers to a literal config value. It could be both a single or a structured (in YAML format) ones.
+
false
+ + +### Stack.spec.configRefs[index][key].configmap +[↩ Parent](#stackspecconfigrefsindexkey) + + + +ConfigMap refers to a Kubernetes ConfigMap. It will be assumed the ConfigMap key content is the stack config in YAML format. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key within the ConfigMap to use.
+
true
namestring + Name of the ConfigMap
+
true
namespacestring + Namespace where the ConfigMap is stored. Deprecated; non-empty values will be considered invalid unless namespace isolation is disabled in the controller.
+
false
+ + +### Stack.spec.configRefs[index][key].env +[↩ Parent](#stackspecconfigrefsindexkey) + + + +Env selects an environment variable set on the operator process + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + Name of the environment variable
+
true
+ + +### Stack.spec.configRefs[index][key].filesystem +[↩ Parent](#stackspecconfigrefsindexkey) + + + +FileSystem selects a file on the operator's file system + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
pathstring + Path on the filesystem to use to load information from.
+
true
+ + +### Stack.spec.configRefs[index][key].literal +[↩ Parent](#stackspecconfigrefsindexkey) + + + +Literal refers to a literal config value. It could be both a single or a structured (in YAML format) ones. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
valueJSON + Value to load
+
true
+ + ### Stack.spec.envRefs[key] [↩ Parent](#stackspec) @@ -2094,6 +2278,13 @@ StackSpec defines the desired state of Pulumi Stack being managed by this operat (optional) Config is the configuration for this stack, which can be optionally specified inline. If this is omitted, configuration is assumed to be checked in and taken from the source repository.
false + + configRefs + []map[string]object + + (optional) ConfigRefs is an optional list of configuration values for this stack, which can be specified through each ConfigRef. If this is omitted, configuration is assumed to be checked in and taken from the source repository. If present, ConfigRefs values will be merged with the ones passed through Config field (ConfigRefs values have precedence, and configs will the same key name will be overwritten).
+ + false continueResyncOnCommitMatch boolean @@ -2247,6 +2438,183 @@ StackSpec defines the desired state of Pulumi Stack being managed by this operat +### Stack.spec.configRefs[index][key] +[↩ Parent](#stackspec-1) + + + +ConfigRef identifies a resource from which config information can be loaded. Environment variables, files on the filesystem, Kubernetes Secrets, ConfigMaps, structured and config literal values strings are currently supported. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
typestring + SelectorType is required and signifies the type of selector. Must be one of: Env, FS, Secret, ConfigMap, Structured, Literal
+
true
configmapobject + ConfigMap refers to a Kubernetes ConfigMap. It will be assumed the ConfigMap key content is the stack config in YAML format.
+
false
envobject + Env selects an environment variable set on the operator process
+
false
filesystemobject + FileSystem selects a file on the operator's file system
+
false
literalobject + Literal refers to a literal config value. It could be both a single or a structured (in YAML format) ones.
+
false
+ + +### Stack.spec.configRefs[index][key].configmap +[↩ Parent](#stackspecconfigrefsindexkey-1) + + + +ConfigMap refers to a Kubernetes ConfigMap. It will be assumed the ConfigMap key content is the stack config in YAML format. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + Key within the ConfigMap to use.
+
true
namestring + Name of the ConfigMap
+
true
namespacestring + Namespace where the ConfigMap is stored. Deprecated; non-empty values will be considered invalid unless namespace isolation is disabled in the controller.
+
false
+ + +### Stack.spec.configRefs[index][key].env +[↩ Parent](#stackspecconfigrefsindexkey-1) + + + +Env selects an environment variable set on the operator process + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + Name of the environment variable
+
true
+ + +### Stack.spec.configRefs[index][key].filesystem +[↩ Parent](#stackspecconfigrefsindexkey-1) + + + +FileSystem selects a file on the operator's file system + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
pathstring + Path on the filesystem to use to load information from.
+
true
+ + +### Stack.spec.configRefs[index][key].literal +[↩ Parent](#stackspecconfigrefsindexkey-1) + + + +Literal refers to a literal config value. It could be both a single or a structured (in YAML format) ones. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
valueJSON + Value to load
+
true
+ + ### Stack.spec.envRefs[key] [↩ Parent](#stackspec-1) diff --git a/pkg/apis/pulumi/shared/stack_types.go b/pkg/apis/pulumi/shared/stack_types.go index 3e6827b9..deb6fc90 100644 --- a/pkg/apis/pulumi/shared/stack_types.go +++ b/pkg/apis/pulumi/shared/stack_types.go @@ -46,11 +46,14 @@ type StackSpec struct { // (optional) Config is the configuration for this stack, which can be optionally specified inline. If this // is omitted, configuration is assumed to be checked in and taken from the source repository. Config map[string]string `json:"config,omitempty"` + // (optional) ConfigRefs is an optional list of configuration values for this stack, which can be specified through each ConfigRef. + // If this is omitted, configuration is assumed to be checked in and taken from the source repository. + // If present, ConfigRefs values will be merged with the ones passed through Config field (ConfigRefs values have precedence, and configs will the same key name will be overwritten). + ConfigRefs []map[string]ConfigRef `json:"configRefs,omitempty"` // (optional) Secrets is the secret configuration for this stack, which can be optionally specified inline. If this // is omitted, secrets configuration is assumed to be checked in and taken from the source repository. // Deprecated: use SecretRefs instead. Secrets map[string]string `json:"secrets,omitempty"` - // (optional) SecretRefs is the secret configuration for this stack which can be specified through ResourceRef. // If this is omitted, secrets configuration is assumed to be checked in and taken from the source repository. SecretRefs map[string]ResourceRef `json:"secretsRef,omitempty"` @@ -235,6 +238,31 @@ type ResourceRef struct { ResourceSelector `json:",inline"` } +// ConfigRef identifies a resource from which config information can be loaded. +// Environment variables, files on the filesystem, Kubernetes Secrets, ConfigMaps, structured and config literal values +// strings are currently supported. +type ConfigRef struct { + // SelectorType is required and signifies the type of selector. Must be one of: + // Env, FS, Secret, ConfigMap, Structured, Literal + SelectorType ConfigResourceSelectorType `json:"type"` + ConfigResourceSelector `json:",inline"` +} + +// ConfigResourceSelector is a union over resource config selectors supporting one of +// filesystem, environment variable, Kubernetes Secret, Kubernetes ConfigMaps, a structured value and literal values. +type ConfigResourceSelector struct { + // FileSystem selects a file on the operator's file system + FileSystem *FSSelector `json:"filesystem,omitempty"` + // Env selects an environment variable set on the operator process + Env *EnvSelector `json:"env,omitempty"` + // ConfigMap refers to a Kubernetes ConfigMap. + // It will be assumed the ConfigMap key content is the stack config in YAML format. + ConfigMap *ConfigMapSelector `json:"configmap,omitempty"` + // Literal refers to a literal config value. + // It could be both a single or a structured (in YAML format) ones. + Literal *ConfigLiteralRef `json:"literal,omitempty"` +} + type ProgramReference struct { // +kubebuilder:validation:Required Name string `json:"name"` @@ -252,6 +280,16 @@ func NewEnvResourceRef(envVarName string) ResourceRef { } } +func NewEnvConfigResourceRef(envVarName string) ConfigRef { + envResourceRef := NewEnvResourceRef(envVarName) + return ConfigRef{ + SelectorType: ConfigResourceSelectorType(envResourceRef.SelectorType), + ConfigResourceSelector: ConfigResourceSelector{ + Env: envResourceRef.Env, + }, + } +} + // NewFileSystemResourceRef creates a new file system resource ref. func NewFileSystemResourceRef(path string) ResourceRef { return ResourceRef{ @@ -264,6 +302,17 @@ func NewFileSystemResourceRef(path string) ResourceRef { } } +// NewConfigFileSystemResourceRef creates a new file system resource ref. +func NewFileSystemConfigResourceRef(path string) ConfigRef { + fsResourceRef := NewFileSystemResourceRef(path) + return ConfigRef{ + SelectorType: ConfigResourceSelectorType(fsResourceRef.SelectorType), + ConfigResourceSelector: ConfigResourceSelector{ + FileSystem: fsResourceRef.FileSystem, + }, + } +} + // NewSecretResourceRef creates a new Secret resource ref. func NewSecretResourceRef(namespace, name, key string) ResourceRef { return ResourceRef{ @@ -290,6 +339,32 @@ func NewLiteralResourceRef(value string) ResourceRef { } } +// NewConfigLiteralResourceRef creates a new structured config resource ref. +func NewConfigLiteralResourceRef(config apiextensionsv1.JSON) ConfigRef { + return ConfigRef{ + SelectorType: ConfigResourceSelectorLiteral, + ConfigResourceSelector: ConfigResourceSelector{ + Literal: &ConfigLiteralRef{ + Value: config, + }, + }, + } +} + +// NewConfigMapConfigResourceRef creates a new ConfigMap resource ref to be used as config. +func NewConfigMapConfigResourceRef(namespace, name, key string) ConfigRef { + return ConfigRef{ + SelectorType: ConfigResourceSelectorConfigMap, + ConfigResourceSelector: ConfigResourceSelector{ + ConfigMap: &ConfigMapSelector{ + Namespace: namespace, + Name: name, + Key: key, + }, + }, + } +} + // ResourceSelectorType identifies the type of the resource reference in type ResourceSelectorType string @@ -304,6 +379,16 @@ const ( ResourceSelectorLiteral = ResourceSelectorType("Literal") ) +// ConfigResourceSelectorType identifies the type of the resource reference in +type ConfigResourceSelectorType string + +const ( + // ConfigResourceSelectorConfigMap indicates the resource is a Kubernetes ConfigMap + ConfigResourceSelectorConfigMap = ConfigResourceSelectorType("ConfigMap") + // ConfigResourceSelectorLiteral indicates the resource is a literal value (simple or structured) + ConfigResourceSelectorLiteral = ConfigResourceSelectorType("Literal") +) + // ResourceSelector is a union over resource selectors supporting one of // filesystem, environment variable, Kubernetes Secret and literal values. type ResourceSelector struct { @@ -340,6 +425,23 @@ type SecretSelector struct { Key string `json:"key"` } +// ConfigMapSelector identifies the information to load from a Kubernetes ConfigMap. +type ConfigMapSelector struct { + // Namespace where the ConfigMap is stored. Deprecated; non-empty values will be considered invalid + // unless namespace isolation is disabled in the controller. + Namespace string `json:"namespace,omitempty"` + // Name of the ConfigMap + Name string `json:"name"` + // Key within the ConfigMap to use. + Key string `json:"key"` +} + +// ConfigLiteralRef identifies a config value to load; it could be a single value, or a structured one +type ConfigLiteralRef struct { + // Value to load + Value apiextensionsv1.JSON `json:"value"` +} + // LiteralRef identifies a literal value to load. type LiteralRef struct { // Value to load diff --git a/pkg/apis/pulumi/shared/zz_generated.deepcopy.go b/pkg/apis/pulumi/shared/zz_generated.deepcopy.go index 9b7b2f91..4e722f37 100644 --- a/pkg/apis/pulumi/shared/zz_generated.deepcopy.go +++ b/pkg/apis/pulumi/shared/zz_generated.deepcopy.go @@ -26,6 +26,88 @@ func (in *BasicAuth) DeepCopy() *BasicAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigLiteralRef) DeepCopyInto(out *ConfigLiteralRef) { + *out = *in + in.Value.DeepCopyInto(&out.Value) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigLiteralRef. +func (in *ConfigLiteralRef) DeepCopy() *ConfigLiteralRef { + if in == nil { + return nil + } + out := new(ConfigLiteralRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapSelector) DeepCopyInto(out *ConfigMapSelector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapSelector. +func (in *ConfigMapSelector) DeepCopy() *ConfigMapSelector { + if in == nil { + return nil + } + out := new(ConfigMapSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigRef) DeepCopyInto(out *ConfigRef) { + *out = *in + in.ConfigResourceSelector.DeepCopyInto(&out.ConfigResourceSelector) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigRef. +func (in *ConfigRef) DeepCopy() *ConfigRef { + if in == nil { + return nil + } + out := new(ConfigRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigResourceSelector) DeepCopyInto(out *ConfigResourceSelector) { + *out = *in + if in.FileSystem != nil { + in, out := &in.FileSystem, &out.FileSystem + *out = new(FSSelector) + **out = **in + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = new(EnvSelector) + **out = **in + } + if in.ConfigMap != nil { + in, out := &in.ConfigMap, &out.ConfigMap + *out = new(ConfigMapSelector) + **out = **in + } + if in.Literal != nil { + in, out := &in.Literal, &out.Literal + *out = new(ConfigLiteralRef) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigResourceSelector. +func (in *ConfigResourceSelector) DeepCopy() *ConfigResourceSelector { + if in == nil { + return nil + } + out := new(ConfigResourceSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvSelector) DeepCopyInto(out *EnvSelector) { *out = *in @@ -342,6 +424,19 @@ func (in *StackSpec) DeepCopyInto(out *StackSpec) { (*out)[key] = val } } + if in.ConfigRefs != nil { + in, out := &in.ConfigRefs, &out.ConfigRefs + *out = make([]map[string]ConfigRef, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make(map[string]ConfigRef, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + } + } if in.Secrets != nil { in, out := &in.Secrets, &out.Secrets *out = make(map[string]string, len(*in)) diff --git a/pkg/controller/stack/stack_config.go b/pkg/controller/stack/stack_config.go new file mode 100644 index 00000000..579af20f --- /dev/null +++ b/pkg/controller/stack/stack_config.go @@ -0,0 +1,92 @@ +package stack + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/pulumi/pulumi/sdk/v3/go/auto" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +type StructuredConfig map[string]any + +type ConfigKeyValue struct { + Key string + Value auto.ConfigValue +} + +func NewStructuredConfigFromJSON(key string, rawValue apiextensionsv1.JSON) (*StructuredConfig, error) { + var data any + if err := json.Unmarshal(rawValue.Raw, &data); err != nil { + return nil, err + } + if dataAsMap, err := data.(map[string]any); err { + structuredConfig := StructuredConfig(dataAsMap) + return &structuredConfig, nil + } + structuredConfig := StructuredConfig(map[string]any{ + key: data, + }) + return &structuredConfig, nil +} + +func (c StructuredConfig) Flatten() []ConfigKeyValue { + flatten := flattenKeys(c) + + configValues := make([]ConfigKeyValue, 0, len(flatten)) + for key, value := range flatten { + configValues = append(configValues, ConfigKeyValue{ + Key: key, + Value: auto.ConfigValue{ + Value: fmt.Sprint(value), + }, + }) + } + + return configValues +} + +func flattenKeys(config StructuredConfig) map[string]any { + output := make(map[string]any) + + for k, v := range config { + flatten(output, v, k) + } + + return output +} + +func flatten(flatMap map[string]any, nested any, prefix string) { + assign := func(newKey string, v any) { + switch v.(type) { + case map[string]any, []any: + flatten(flatMap, v, newKey) + default: + flatMap[newKey] = v + } + } + + switch nested.(type) { + case map[string]any: + for k, v := range nested.(map[string]any) { + newKey := enkey(prefix, k) + assign(newKey, v) + } + case []any: + for i, v := range nested.([]any) { + newKey := indexedKey(prefix, strconv.Itoa(i)) + assign(newKey, v) + } + default: + assign(prefix, nested) + } +} + +func enkey(prefix, subkey string) string { + return fmt.Sprintf("%s.%s", prefix, subkey) +} + +func indexedKey(prefix, subkey string) string { + return fmt.Sprintf("%s[%s]", prefix, subkey) +} diff --git a/pkg/controller/stack/stack_config_test.go b/pkg/controller/stack/stack_config_test.go new file mode 100644 index 00000000..80bd4df2 --- /dev/null +++ b/pkg/controller/stack/stack_config_test.go @@ -0,0 +1,245 @@ +package stack + +import ( + "encoding/json" + "testing" + + "github.com/pulumi/pulumi/sdk/v3/go/auto" + "github.com/stretchr/testify/assert" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +func toJson(v any) apiextensionsv1.JSON { + b, err := json.Marshal(v) + if err != nil { + panic(err) + } + return apiextensionsv1.JSON{Raw: b} +} + +func TestNewLiteralConfigFromJson(t *testing.T) { + structuredConfig, err := NewStructuredConfigFromJSON("sample", toJson("just-a-value")) + if assert.NoError(t, err) { + configValues := structuredConfig.Flatten() + expected := []ConfigKeyValue{ + { + Key: "sample", + Value: auto.ConfigValue{ + Value: "just-a-value", + Secret: false, + }, + }, + } + + assert.ElementsMatch(t, expected, configValues) + } +} + +func TestNewStructuredConfigFromJson(t *testing.T) { + toJson := func(v any) apiextensionsv1.JSON { + b, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + return apiextensionsv1.JSON{} + } + return apiextensionsv1.JSON{Raw: b} + } + + check := func(sourceConfigMap map[string]any, expected []ConfigKeyValue) { + structuredConfig, err := NewStructuredConfigFromJSON("", toJson(sourceConfigMap)) + if assert.NoError(t, err) { + configValues := structuredConfig.Flatten() + assert.ElementsMatch(t, expected, configValues) + } + } + + t.Run("We should be able to handle simple key-value pairs", func(t *testing.T) { + sourceConfigMap := map[string]any{ + "aws:region": "us-east-1", + } + expected := []ConfigKeyValue{ + { + Key: "aws:region", + Value: auto.ConfigValue{ + Value: "us-east-1", + Secret: false, + }, + }, + } + check(sourceConfigMap, expected) + }) + t.Run("We should be able to handle a structured, complex (namespaced) config", func(t *testing.T) { + sourceConfigMap := map[string]any{ + "aws:assumeRole": map[string]any{ + "roleArn": "my-role-arn", + "sessionName": "my-session-name", + }, + "aws:defaultTags": map[string]any{ + "tags": map[string]any{ + "my-tag": "tag-value", + }, + }, + } + expected := []ConfigKeyValue{ + { + Key: "aws:assumeRole.roleArn", + Value: auto.ConfigValue{ + Value: "my-role-arn", + Secret: false, + }, + }, + { + Key: "aws:assumeRole.sessionName", + Value: auto.ConfigValue{ + Value: "my-session-name", + Secret: false, + }, + }, + { + Key: "aws:defaultTags.tags.my-tag", + Value: auto.ConfigValue{ + Value: "tag-value", + Secret: false, + }, + }, + } + check(sourceConfigMap, expected) + }) + + t.Run("We should be able to handle a structured, complex (non-namespaced) config", func(t *testing.T) { + sourceConfigMap := map[string]any{ + "an-object-config": map[string]any{ + "another-config": map[string]any{ + "config-key": "value", + }, + "a-nested-list-config": []any{"one", "two", "three"}, + }, + } + expected := []ConfigKeyValue{ + { + Key: "an-object-config.a-nested-list-config[0]", + Value: auto.ConfigValue{ + Value: "one", + Secret: false, + }, + }, + { + Key: "an-object-config.a-nested-list-config[1]", + Value: auto.ConfigValue{ + Value: "two", + Secret: false, + }, + }, + { + Key: "an-object-config.a-nested-list-config[2]", + Value: auto.ConfigValue{ + Value: "three", + Secret: false, + }, + }, + { + Key: "an-object-config.another-config.config-key", + Value: auto.ConfigValue{ + Value: "value", + Secret: false, + }, + }, + } + check(sourceConfigMap, expected) + }) + + t.Run("We should be able to handle simple, non-string config values", func(t *testing.T) { + sourceConfigMap := map[string]any{ + "a-list-config": []any{"a", "b", "c"}, + "a-simple-config": "just-a-simple-value", + "a-boolean-config": true, + "an-integer-config": 123456, + } + expected := []ConfigKeyValue{ + { + Key: "a-boolean-config", + Value: auto.ConfigValue{ + Value: "true", + Secret: false, + }, + }, + { + Key: "a-list-config[0]", + Value: auto.ConfigValue{ + Value: "a", + Secret: false, + }, + }, + { + Key: "a-list-config[1]", + Value: auto.ConfigValue{ + Value: "b", + Secret: false, + }, + }, + { + Key: "a-list-config[2]", + Value: auto.ConfigValue{ + Value: "c", + Secret: false, + }, + }, + { + Key: "a-simple-config", + Value: auto.ConfigValue{ + Value: "just-a-simple-value", + Secret: false, + }, + }, + { + Key: "an-integer-config", + Value: auto.ConfigValue{ + Value: "123456", + Secret: false, + }, + }, + } + check(sourceConfigMap, expected) + }) + t.Run("We should be able to handle a structured, complex (non-namespaced) config", func(t *testing.T) { + sourceConfigMap := map[string]any{ + "an-object-config": map[string]any{ + "another-config": map[string]any{ + "config-key": "value", + }, + "a-nested-list-config": []any{"one", "two", "three"}, + }, + } + expected := []ConfigKeyValue{ + { + Key: "an-object-config.a-nested-list-config[0]", + Value: auto.ConfigValue{ + Value: "one", + Secret: false, + }, + }, + { + Key: "an-object-config.a-nested-list-config[1]", + Value: auto.ConfigValue{ + Value: "two", + Secret: false, + }, + }, + { + Key: "an-object-config.a-nested-list-config[2]", + Value: auto.ConfigValue{ + Value: "three", + Secret: false, + }, + }, + { + Key: "an-object-config.another-config.config-key", + Value: auto.ConfigValue{ + Value: "value", + Secret: false, + }, + }, + } + check(sourceConfigMap, expected) + }) +} diff --git a/pkg/controller/stack/stack_controller.go b/pkg/controller/stack/stack_controller.go index eab29871..cc694105 100644 --- a/pkg/controller/stack/stack_controller.go +++ b/pkg/controller/stack/stack_controller.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "sort" "strconv" "strings" "sync" @@ -1085,6 +1086,64 @@ func (sess *reconcileStackSession) SetEnvRefsForWorkspace(ctx context.Context, w return nil } +func (sess *reconcileStackSession) resolveConfigRefs(ctx context.Context) ([]ConfigKeyValue, error) { + allConfigs := make([]ConfigKeyValue, 0) + for _, configRefMap := range sess.stack.ConfigRefs { + for k, configRef := range configRefMap { + // ConfigMap and Literal are special config cases, so they are checked first + switch configRef.SelectorType { + case shared.ConfigResourceSelectorConfigMap: + configMapRef := configRef.ConfigMap + if configMapRef != nil { + var config corev1.ConfigMap + if err := sess.kubeClient.Get(ctx, types.NamespacedName{Name: configMapRef.Name, Namespace: configMapRef.Namespace}, &config); err != nil { + return nil, fmt.Errorf("Failed to get the ConfigMap %s on namespace %s: %w", configMapRef.Name, configMapRef.Namespace, err) + } + // assumes the whole configmaps's data is the config content; try to read as a conventional stack yaml config + var configMapContent map[string]any + if err := yaml.Unmarshal([]byte(config.Data[configMapRef.Key]), &configMapContent); err != nil { + return nil, fmt.Errorf("Failed to read the ConfigMap content as a stack YAML config. Namespace=%s Name=%s: %w", configMapRef.Namespace, configMapRef.Name, err) + } + structuredConfig := StructuredConfig(configMapContent).Flatten() + allConfigs = append(allConfigs, structuredConfig...) + } + case shared.ConfigResourceSelectorLiteral: + literalRef := configRef.Literal + if literalRef != nil { + // ConfigLiteralRef handles both simple and structured values as json, flattening all keys to build a list of Pulumi key:value configs + structuredConfig, err := NewStructuredConfigFromJSON(k, literalRef.Value) + if err != nil { + return nil, fmt.Errorf("Failed to unmarshall %s as a literal config: %w", k, err) + } + configs := structuredConfig.Flatten() + allConfigs = append(allConfigs, configs...) + } + default: + // try to resolve as a ResourceRef + value, err := sess.resolveResourceRef(ctx, &shared.ResourceRef{ + SelectorType: shared.ResourceSelectorType(configRef.SelectorType), + ResourceSelector: shared.ResourceSelector{ + FileSystem: configRef.FileSystem, + Env: configRef.Env, + }, + }) + if err != nil { + return nil, err + } + allConfigs = append(allConfigs, ConfigKeyValue{ + Key: k, + Value: auto.ConfigValue{ + Value: value, + Secret: false, + }, + }) + } + } + } + + return allConfigs, nil +} + func (sess *reconcileStackSession) resolveResourceRef(ctx context.Context, ref *shared.ResourceRef) (string, error) { switch ref.SelectorType { case shared.ResourceSelectorEnv: @@ -1556,20 +1615,46 @@ func (sess *reconcileStackSession) InstallProjectDependencies(ctx context.Contex } func (sess *reconcileStackSession) UpdateConfig(ctx context.Context) error { - m := make(auto.ConfigMap) + // Initialize a single config value slice to all values; + // plain config will be handled with values from ConfigRefs + configValues := make([]ConfigKeyValue, 0, len(sess.stack.Config)) + for k, v := range sess.stack.Config { - m[k] = auto.ConfigValue{ - Value: v, - Secret: false, - } + configValues = append(configValues, ConfigKeyValue{ + Key: k, + Value: auto.ConfigValue{ + Value: v, + Secret: false, + }, + }) + } + + // appending ConfigRefs values + configValuesFromRefs, err := sess.resolveConfigRefs(ctx) + if err != nil { + return fmt.Errorf("Fail reading ConfigRef values: %w", err) + } + configValues = append(configValues, configValuesFromRefs...) + + // config values should be ordered by key to avoid problems like try to set 'c[1]=value1' before 'c[0]=value2' + sort.Slice(configValues, func(i, j int) bool { + return configValues[i].Key < configValues[j].Key + }) + + // set all configs as path=true + for _, v := range configValues { + sess.autoStack.SetConfigWithOptions(ctx, v.Key, v.Value, &auto.ConfigOptions{ + Path: true, + }) } + + m := make(auto.ConfigMap) for k, v := range sess.stack.Secrets { m[k] = auto.ConfigValue{ Value: v, Secret: true, } } - for k, ref := range sess.stack.SecretRefs { resolved, err := sess.resolveResourceRef(ctx, &ref) if err != nil { @@ -1794,7 +1879,7 @@ func (sess *reconcileStackSession) addDefaultPermalink(ctx context.Context, stac // Get stack URL. info, err := sess.autoStack.Info(ctx) if err != nil { - sess.logger.Error(err, "Failed to update Stack status with default permalink", "Stack.Name", stack.Spec.Stack) + sess.logger.Error(err, "Fail to update Stack status with default permalink", "Stack.Name", stack.Spec.Stack) return err } // Set stack URL. diff --git a/test/stack_controller_test.go b/test/stack_controller_test.go index 86064ddb..a7c90a62 100644 --- a/test/stack_controller_test.go +++ b/test/stack_controller_test.go @@ -4,6 +4,7 @@ package tests import ( "context" + "encoding/json" "fmt" "os" "os/exec" @@ -14,6 +15,7 @@ import ( "github.com/pulumi/pulumi-kubernetes-operator/pkg/apis/pulumi/shared" pulumiv1 "github.com/pulumi/pulumi-kubernetes-operator/pkg/apis/pulumi/v1" + "sigs.k8s.io/yaml" git "github.com/go-git/go-git/v5" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -205,6 +207,429 @@ var _ = Describe("Stack Controller", func() { }) }) + Context("configuring a stack using ConfigRefs", func() { + var stack *pulumiv1.Stack + + When("using a FileSystemRef", func() { + var configDir string + + BeforeEach(func() { + By("Creating directory to store configs") + configDir, err = os.MkdirTemp("", "secrets") + if err != nil { + Fail("Failed to create config temp directory") + } + Expect(os.WriteFile(filepath.Join(configDir, "word.txt"), []byte("just-a-word-in-a-file"), 0600)).To(Succeed()) + }) + + AfterEach(func() { + deleteAndWaitForFinalization(stack) + }) + + It("can deploy a stack reading a config from a file", func() { + + // Use a local backend for this test. + // Local backend doesn't allow setting slashes in stack name. + const stackName = "dev" + fmt.Fprintf(GinkgoWriter, "Stack.Name: %s\n", stackName) + + // Define the stack spec + localSpec := shared.StackSpec{ + Backend: fmt.Sprintf("file://%s", backendDir), + Stack: stackName, + GitSource: &shared.GitSource{ + ProjectRepo: baseDir, + RepoDir: "test/testdata/config-refs", + Commit: commit, + }, + SecretsProvider: "passphrase", + EnvRefs: defaultEnvRefs(), + ConfigRefs: []map[string]shared.ConfigRef{ + { + "word": shared.NewFileSystemConfigResourceRef(filepath.Join(configDir, "word.txt")), + }, + }, + Refresh: true, + } + + // Create the stack + name := "config-refs-with-file-stack" + stack = generateStackV1(name, namespace, localSpec) + Expect(k8sClient.Create(ctx, stack)).Should(Succeed()) + + // Check that the stack updated successfully + fetched := &pulumiv1.Stack{} + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: stack.Name, Namespace: namespace}, fetched) + if err != nil { + return false + } + return stackUpdatedToCommit(fetched.Status.LastUpdate, stack.Spec.Commit) + }, stackExecTimeout, interval).Should(BeTrue()) + // Validate outputs. + Expect(fetched.Status.Outputs).Should(HaveKeyWithValue("word", v1.JSON{Raw: []byte(`"just-a-word-in-a-file"`)})) + }) + }) + + When("using an EnvRef", func() { + AfterEach(func() { + deleteAndWaitForFinalization(stack) + }) + + It("can deploy a stack reading a config from an EnvVar", func() { + + // Use a local backend for this test. + // Local backend doesn't allow setting slashes in stack name. + const stackName = "dev" + fmt.Fprintf(GinkgoWriter, "Stack.Name: %s\n", stackName) + + err := os.Setenv("WORD", "just-a-word") + if err != nil { + Fail("Unable to set WORD environment variable.") + } + + // Define the stack spec + localSpec := shared.StackSpec{ + Backend: fmt.Sprintf("file://%s", backendDir), + Stack: stackName, + GitSource: &shared.GitSource{ + ProjectRepo: baseDir, + RepoDir: "test/testdata/config-refs", + Commit: commit, + }, + SecretsProvider: "passphrase", + EnvRefs: defaultEnvRefs(), + ConfigRefs: []map[string]shared.ConfigRef{ + { + "word": shared.NewEnvConfigResourceRef("WORD"), + }, + }, + Refresh: true, + } + + // Create the stack + name := "config-refs-with-envs-stack" + stack = generateStackV1(name, namespace, localSpec) + Expect(k8sClient.Create(ctx, stack)).Should(Succeed()) + + // Check that the stack updated successfully + fetched := &pulumiv1.Stack{} + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: stack.Name, Namespace: namespace}, fetched) + if err != nil { + return false + } + return stackUpdatedToCommit(fetched.Status.LastUpdate, stack.Spec.Commit) + }, stackExecTimeout, interval).Should(BeTrue()) + // Validate outputs. + Expect(fetched.Status.Outputs).Should(HaveKeyWithValue("word", v1.JSON{Raw: []byte(`"just-a-word"`)})) + }) + }) + + When("using a SecretRef", func() { + var configSecret *corev1.Secret + + BeforeEach(func() { + // Create the config secret + By("Creating the Config Secret") + configSecret = generateSecret("config-secret", namespace, + map[string][]byte{ + "secret-word": []byte("just-a-secret-word"), + }, + ) + Expect(k8sClient.Create(ctx, configSecret)).Should(Succeed()) + DeferCleanup(func() { + if configSecret != nil { + By("Deleting the Config Secret") + Expect(k8sClient.Delete(ctx, configSecret)).Should(Succeed()) + } + }) + }) + + AfterEach(func() { + deleteAndWaitForFinalization(stack) + }) + }) + + When("using a ConfigLiteralRef, with a simple value", func() { + AfterEach(func() { + deleteAndWaitForFinalization(stack) + }) + + It("can deploy a stack reading a config from a Literal value", func() { + + // Use a local backend for this test. + // Local backend doesn't allow setting slashes in stack name. + const stackName = "dev" + fmt.Fprintf(GinkgoWriter, "Stack.Name: %s\n", stackName) + + jsonValue := v1.JSON{Raw: []byte(`"just-a-literal-word"`)} + + // Define the stack spec + localSpec := shared.StackSpec{ + Backend: fmt.Sprintf("file://%s", backendDir), + Stack: stackName, + GitSource: &shared.GitSource{ + ProjectRepo: baseDir, + RepoDir: "test/testdata/config-refs", + Commit: commit, + }, + SecretsProvider: "passphrase", + EnvRefs: defaultEnvRefs(), + ConfigRefs: []map[string]shared.ConfigRef{ + { + "word": shared.NewConfigLiteralResourceRef(jsonValue), + }, + }, + Refresh: true, + } + + // Create the stack + name := "config-refs-with-literal-stack" + stack = generateStackV1(name, namespace, localSpec) + Expect(k8sClient.Create(ctx, stack)).Should(Succeed()) + + // Check that the stack updated successfully + fetched := &pulumiv1.Stack{} + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: stack.Name, Namespace: namespace}, fetched) + if err != nil { + return false + } + return stackUpdatedToCommit(fetched.Status.LastUpdate, stack.Spec.Commit) + }, stackExecTimeout, interval).Should(BeTrue()) + // Validate outputs. + Expect(fetched.Status.Outputs).Should(HaveKeyWithValue("word", jsonValue)) + }) + }) + + When("using a ConfigLiteralRef, with a structured, complex value", func() { + AfterEach(func() { + deleteAndWaitForFinalization(stack) + }) + + It("can deploy a stack reading a config from a literal (structured) value", func() { + + // Use a local backend for this test. + // Local backend doesn't allow setting slashes in stack name. + const stackName = "dev" + fmt.Fprintf(GinkgoWriter, "Stack.Name: %s\n", stackName) + + structuredConfig := map[string]any{ + "structured": map[string]any{ + "nested": map[string]any{ + "field": "just-a-structured-value", + }, + }, + } + jsonStructuredConfig, err := json.Marshal(structuredConfig) + if err != nil { + Fail("Failed to serialize a structured config to json.") + } + + // Define the stack spec + localSpec := shared.StackSpec{ + Backend: fmt.Sprintf("file://%s", backendDir), + Stack: stackName, + GitSource: &shared.GitSource{ + ProjectRepo: baseDir, + RepoDir: "test/testdata/structured-config-refs", + Commit: commit, + }, + SecretsProvider: "passphrase", + EnvRefs: defaultEnvRefs(), + ConfigRefs: []map[string]shared.ConfigRef{ + { + "structured": shared.NewConfigLiteralResourceRef(v1.JSON{Raw: jsonStructuredConfig}), + }, + }, + Refresh: true, + } + + // Create the stack + name := "config-refs-with-literal-stack" + stack = generateStackV1(name, namespace, localSpec) + Expect(k8sClient.Create(ctx, stack)).Should(Succeed()) + + // Check that the stack updated successfully + fetched := &pulumiv1.Stack{} + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: stack.Name, Namespace: namespace}, fetched) + if err != nil { + return false + } + return stackUpdatedToCommit(fetched.Status.LastUpdate, stack.Spec.Commit) + }, stackExecTimeout, interval).Should(BeTrue()) + // Validate outputs. + Expect(fetched.Status.Outputs).Should(BeEquivalentTo(shared.StackOutputs{ + "nested-config-field": v1.JSON{Raw: []byte(`"just-a-structured-value"`)}, + })) + }) + }) + + When("using a ConfigMapRef", func() { + var configMap *corev1.ConfigMap + + BeforeEach(func() { + // Create the configmap + By("Creating the ConfigMap") + + structuredConfig := map[string]any{ + "structured": map[string]any{ + "nested": map[string]any{ + "field": "just-a-structured-value", + }, + }, + } + + structuredConfigAsYaml, err := yaml.Marshal(structuredConfig) + if err != nil { + Fail("Failed to serialize a structured config to yaml.") + } + + configMap = generateConfigMap("config-secret", namespace, map[string]string{ + "Pulumi.dev.yaml": string(structuredConfigAsYaml), + }) + Expect(k8sClient.Create(ctx, configMap)).Should(Succeed()) + DeferCleanup(func() { + if configMap != nil { + By("Deleting the ConfigMap") + Expect(k8sClient.Delete(ctx, configMap)).Should(Succeed()) + } + }) + }) + + AfterEach(func() { + deleteAndWaitForFinalization(stack) + }) + + It("can deploy a stack reading a config from a ConfigMap", func() { + + // Use a local backend for this test. + // Local backend doesn't allow setting slashes in stack name. + const stackName = "dev" + fmt.Fprintf(GinkgoWriter, "Stack.Name: %s\n", stackName) + + // Define the stack spec + localSpec := shared.StackSpec{ + Backend: fmt.Sprintf("file://%s", backendDir), + Stack: stackName, + GitSource: &shared.GitSource{ + ProjectRepo: baseDir, + RepoDir: "test/testdata/structured-config-refs", + Commit: commit, + }, + SecretsProvider: "passphrase", + EnvRefs: defaultEnvRefs(), + ConfigRefs: []map[string]shared.ConfigRef{ + { + "stack-config": shared.NewConfigMapConfigResourceRef(namespace, configMap.Name, "Pulumi.dev.yaml"), + }, + }, + Refresh: true, + } + + // Create the stack + name := "config-refs-with-literal-stack" + stack = generateStackV1(name, namespace, localSpec) + Expect(k8sClient.Create(ctx, stack)).Should(Succeed()) + + // Check that the stack updated successfully + fetched := &pulumiv1.Stack{} + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: stack.Name, Namespace: namespace}, fetched) + if err != nil { + return false + } + return stackUpdatedToCommit(fetched.Status.LastUpdate, stack.Spec.Commit) + }, stackExecTimeout, interval).Should(BeTrue()) + // Validate outputs. + Expect(fetched.Status.Outputs).Should(BeEquivalentTo(shared.StackOutputs{ + "nested-config-field": v1.JSON{Raw: []byte(`"just-a-structured-value"`)}, + })) + }) + }) + + When("using multiple ConfigRef values", func() { + AfterEach(func() { + deleteAndWaitForFinalization(stack) + }) + + It("can deploy a stack reading a config from multiple ConfigRef values; the order should be predictable", func() { + + // Use a local backend for this test. + // Local backend doesn't allow setting slashes in stack name. + const stackName = "dev" + fmt.Fprintf(GinkgoWriter, "Stack.Name: %s\n", stackName) + + structuredConfig1 := map[string]any{ + "structured": map[string]any{ + "nested": map[string]any{ + "field": "i-am-the-first-value", + }, + }, + } + jsonStructuredConfig1, err := json.Marshal(structuredConfig1) + if err != nil { + Fail("Failed to serialize a structured config to json.") + } + + structuredConfig2 := map[string]any{ + "structured": map[string]any{ + "nested": map[string]any{ + "field": "i-am-the-second-value", + }, + }, + } + jsonStructuredConfig2, err := json.Marshal(structuredConfig2) + if err != nil { + Fail("Failed to serialize a structured config to json.") + } + + // Define the stack spec + localSpec := shared.StackSpec{ + Backend: fmt.Sprintf("file://%s", backendDir), + Stack: stackName, + GitSource: &shared.GitSource{ + ProjectRepo: baseDir, + RepoDir: "test/testdata/structured-config-refs", + Commit: commit, + }, + SecretsProvider: "passphrase", + EnvRefs: defaultEnvRefs(), + ConfigRefs: []map[string]shared.ConfigRef{ + { + "structured": shared.NewConfigLiteralResourceRef(v1.JSON{Raw: jsonStructuredConfig1}), + }, + { + "structured": shared.NewConfigLiteralResourceRef(v1.JSON{Raw: jsonStructuredConfig2}), + }, + }, + Refresh: true, + } + + // Create the stack + name := "config-refs-with-multiple-values-stack" + stack = generateStackV1(name, namespace, localSpec) + Expect(k8sClient.Create(ctx, stack)).Should(Succeed()) + + // Check that the stack updated successfully + fetched := &pulumiv1.Stack{} + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: stack.Name, Namespace: namespace}, fetched) + if err != nil { + return false + } + return stackUpdatedToCommit(fetched.Status.LastUpdate, stack.Spec.Commit) + }, stackExecTimeout, interval).Should(BeTrue()) + // Validate outputs. + Expect(fetched.Status.Outputs).Should(BeEquivalentTo(shared.StackOutputs{ + "nested-config-field": v1.JSON{Raw: []byte(`"i-am-the-second-value"`)}, + })) + }) + }) + }) + Context("Using the AWS provider", func() { var stack *pulumiv1.Stack @@ -494,3 +919,17 @@ func generateSecret(name, namespace string, data map[string][]byte) *corev1.Secr Type: "Opaque", } } + +func generateConfigMap(name, namespace string, data map[string]string) *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "apps/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: strings.Join([]string{name, randString()}, "-"), + Namespace: namespace, + }, + Data: data, + } +} diff --git a/test/testdata/config-refs/Pulumi.dev.yaml b/test/testdata/config-refs/Pulumi.dev.yaml new file mode 100644 index 00000000..e69de29b diff --git a/test/testdata/config-refs/Pulumi.yaml b/test/testdata/config-refs/Pulumi.yaml new file mode 100644 index 00000000..250013ea --- /dev/null +++ b/test/testdata/config-refs/Pulumi.yaml @@ -0,0 +1,14 @@ +name: config-refs +runtime: yaml +description: just a simple project to test config injection +config: + word: + type: string + default: default-word + secret-word: + type: string + default: i-am-a-secret + secret: true +outputs: + word: ${word} + secret-word: ${secret-word} \ No newline at end of file diff --git a/test/testdata/structured-config-refs/Pulumi.dev.yaml b/test/testdata/structured-config-refs/Pulumi.dev.yaml new file mode 100644 index 00000000..e69de29b diff --git a/test/testdata/structured-config-refs/Pulumi.yaml b/test/testdata/structured-config-refs/Pulumi.yaml new file mode 100644 index 00000000..093b910e --- /dev/null +++ b/test/testdata/structured-config-refs/Pulumi.yaml @@ -0,0 +1,4 @@ +name: structured-config-refs +runtime: go +description: Just a simple program to test structured configs + diff --git a/test/testdata/structured-config-refs/go.mod b/test/testdata/structured-config-refs/go.mod new file mode 100644 index 00000000..5fdfadb5 --- /dev/null +++ b/test/testdata/structured-config-refs/go.mod @@ -0,0 +1,90 @@ +module structured-config-refs + +go 1.21 + +require github.com/pulumi/pulumi/sdk/v3 v3.113.3 + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/charmbracelet/bubbles v0.16.1 // indirect + github.com/charmbracelet/bubbletea v0.24.2 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/cheggaaa/pb v1.0.29 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/djherbis/times v1.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.1.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl/v2 v2.17.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pgavlin/fx v0.1.6 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/term v1.1.0 // indirect + github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect + github.com/pulumi/esc v0.6.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zclconf/go-cty v1.13.2 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/grpc v1.57.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/frand v1.4.2 // indirect +) diff --git a/test/testdata/structured-config-refs/go.sum b/test/testdata/structured-config-refs/go.sum new file mode 100644 index 00000000..638ad4d9 --- /dev/null +++ b/test/testdata/structured-config-refs/go.sum @@ -0,0 +1,319 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= +github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= +github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= +github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= +github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= +github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY= +github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= +github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU= +github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= +github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0= +github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= +github.com/pulumi/esc v0.6.2 h1:+z+l8cuwIauLSwXQS0uoI3rqB+YG4SzsZYtHfNoXBvw= +github.com/pulumi/esc v0.6.2/go.mod h1:jNnYNjzsOgVTjCp0LL24NsCk8ZJxq4IoLQdCT0X7l8k= +github.com/pulumi/pulumi/sdk/v3 v3.113.0 h1:CIlmxJZdjxpPPoFe/rrP1dWTwh3CB7ahs/dA6SHcbuE= +github.com/pulumi/pulumi/sdk/v3 v3.113.0/go.mod h1:JWSzKBoHd8rlncC1DhXLf7YdV+Bk/Qf+hSZOOQh0WwQ= +github.com/pulumi/pulumi/sdk/v3 v3.113.3 h1:ySNxoL+O9TtS9WiPa9SWu6mthJryryRzP0kyYaQlNaU= +github.com/pulumi/pulumi/sdk/v3 v3.113.3/go.mod h1:JWSzKBoHd8rlncC1DhXLf7YdV+Bk/Qf+hSZOOQh0WwQ= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= +github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 h1:X9dsIWPuuEJlPX//UmRKophhOKCGXc46RVIGuttks68= +github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7/go.mod h1:UxoP3EypF8JfGEjAII8jx1q8rQyDnX8qdTCs/UQBVIE= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= +github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= +google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= +google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= +lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= +pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA= +pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/test/testdata/structured-config-refs/main.go b/test/testdata/structured-config-refs/main.go new file mode 100644 index 00000000..0c82c58b --- /dev/null +++ b/test/testdata/structured-config-refs/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + myConfig := config.New(ctx, "") + + structuredConfig := StructuredConfig{} + myConfig.RequireObject("structured", &structuredConfig) + + ctx.Export("nested-config-field", pulumi.String(structuredConfig.Nested.Field)) + return nil + }) +} + +type StructuredConfig struct { + Nested NestedConfig +} + +type NestedConfig struct { + Field string +}