From 8c4a065063c4d5bc2e8e822ab0749649e05be578 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:00:00 -0800 Subject: [PATCH 01/30] initial rotate verb --- environment.go | 9 +++++++++ eval/eval.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++---- provider.go | 7 +++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/environment.go b/environment.go index 551314ab..0bb47a59 100644 --- a/environment.go +++ b/environment.go @@ -160,6 +160,15 @@ type EvaluatedExecutionContext struct { // Schema contains the schema for Properties. Schema *schema.Schema `json:"schema,omitempty"` + + // Patches contains updated values that should be written back into the environment definition. + Patches []*Patch +} + +// Patch represents a value that should be written back to the environment at the given path. +type Patch struct { + DocPath string + Replacement Value } // An Environment contains the result of evaluating an environment definition. diff --git a/eval/eval.go b/eval/eval.go index 31945a0c..a30a7f4f 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -83,7 +83,7 @@ func EvalEnvironment( environments EnvironmentLoader, execContext *esc.ExecContext, ) (*esc.Environment, syntax.Diagnostics) { - return evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true) + return evalEnvironment(ctx, false, false, name, env, decrypter, providers, environments, execContext, true) } // CheckEnvironment symbolically evaluates the given environment. Calls to fn::open are not invoked, and instead @@ -98,13 +98,28 @@ func CheckEnvironment( execContext *esc.ExecContext, showSecrets bool, ) (*esc.Environment, syntax.Diagnostics) { - return evalEnvironment(ctx, true, name, env, decrypter, providers, environments, execContext, showSecrets) + return evalEnvironment(ctx, true, false, name, env, decrypter, providers, environments, execContext, showSecrets) +} + +// RotateEnvironment evaluates the given environment and invokes provider rotate methods. +// Updated rotation state will output via execution context patches, and is expected to be written back to the environment definition by the caller. +func RotateEnvironment( + ctx context.Context, + name string, + env *ast.EnvironmentDecl, + decrypter Decrypter, + providers ProviderLoader, + environments EnvironmentLoader, + execContext *esc.ExecContext, +) (*esc.Environment, syntax.Diagnostics) { + return evalEnvironment(ctx, false, true, name, env, decrypter, providers, environments, execContext, true) } // evalEnvironment evaluates an environment and exports the result of evaluation. func evalEnvironment( ctx context.Context, validating bool, + rotating bool, name string, env *ast.EnvironmentDecl, decrypter Decrypter, @@ -117,7 +132,7 @@ func evalEnvironment( return nil, nil } - ec := newEvalContext(ctx, validating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets) + ec := newEvalContext(ctx, validating, rotating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets) v, diags := ec.evaluate() s := schema.Never().Schema() @@ -132,6 +147,7 @@ func evalEnvironment( executionContext := &esc.EvaluatedExecutionContext{ Properties: ec.myContext.export(name).Value.(map[string]esc.Value), Schema: ec.myContext.schema, + Patches: ec.patchOutputs, } return &esc.Environment{ @@ -165,12 +181,16 @@ type evalContext struct { root *expr // the root expression base *value // the base value + rotating bool // true if providers should be rotated + patchOutputs []*esc.Patch // updated rotation state to be written back to the environment definition + diags syntax.Diagnostics // diagnostics generated during evaluation } func newEvalContext( ctx context.Context, validating bool, + rotating bool, name string, env *ast.EnvironmentDecl, decrypter Decrypter, @@ -183,6 +203,7 @@ func newEvalContext( return &evalContext{ ctx: ctx, validating: validating, + rotating: rotating, showSecrets: showSecrets, name: name, env: env, @@ -462,7 +483,8 @@ func (e *evalContext) evaluateImport(myImports map[string]*value, decl *ast.Impo return } - imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets) + // only rotate root environment + imp := newEvalContext(e.ctx, e.validating, false, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets) v, diags := imp.evaluate() e.diags.Extend(diags...) @@ -923,6 +945,29 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { return v } + // if rotating, invoke prior to open + if e.rotating { + if rotator, ok := provider.(esc.Rotator); ok { + newState, err := rotator.Rotate(e.ctx, inputs.export("").Value.(map[string]esc.Value)) + if err != nil { + e.errorf(repr.syntax(), "rotate: %s", err.Error()) + v.unknown = true + return v + } + + // path to provider arguments differs for long vs short form + inputsPath := repr.node.Name().GetValue() + if inputsPath == "fn::open" { + inputsPath += ".inputs" + } + + e.patchOutputs = append(e.patchOutputs, &esc.Patch{ + DocPath: x.path + "." + inputsPath + ".state", + Replacement: newState, + }) + } + } + output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), e.execContext) if err != nil { e.errorf(repr.syntax(), "%s", err.Error()) diff --git a/provider.go b/provider.go index 153cbaa2..d72b4bca 100644 --- a/provider.go +++ b/provider.go @@ -29,3 +29,10 @@ type Provider interface { // Open retrieves the provider's secrets. Open(ctx context.Context, inputs map[string]Value, executionContext EnvExecContext) (Value, error) } + +type Rotator interface { + // Rotate rotates the provider's secret. + // The result represents the new state which will be written back into the environment under the `state` key of the provider's inputs. + // Any secret values in the result will be wrapped by fn::secret in the written result. + Rotate(ctx context.Context, inputs map[string]Value) (Value, error) +} From 4010ab8a413b7333671e5d10b4ef29acebfc4377 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:29:44 -0800 Subject: [PATCH 02/30] example --- environment.go | 6 --- eval/eval_test.go | 32 +++++++++++++++ eval/rotate_example_test.go | 65 +++++++++++++++++++++++++++++++ patch.go | 77 +++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 eval/rotate_example_test.go create mode 100644 patch.go diff --git a/environment.go b/environment.go index 0bb47a59..df3b302e 100644 --- a/environment.go +++ b/environment.go @@ -165,12 +165,6 @@ type EvaluatedExecutionContext struct { Patches []*Patch } -// Patch represents a value that should be written back to the environment at the given path. -type Patch struct { - DocPath string - Replacement Value -} - // An Environment contains the result of evaluating an environment definition. type Environment struct { // Exprs contains the AST for each expression in the environment definition. diff --git a/eval/eval_test.go b/eval/eval_test.go index 22dc8039..2894e815 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -136,6 +136,36 @@ func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, conte return esc.NewValue(inputs), nil } +type swapProvider struct{} + +func (swapProvider) Schema() (*schema.Schema, *schema.Schema) { + inputSchema := schema.Record(schema.BuilderMap{ + "state": schema.Record(schema.BuilderMap{ + "a": schema.String(), + "b": schema.String(), + }), + }).Schema() + outputSchema := schema.Record(schema.BuilderMap{ + "a": schema.String(), + "b": schema.String(), + }).Schema() + return inputSchema, outputSchema +} + +func (swapProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { + state := inputs["state"].Value.(map[string]esc.Value) + return esc.NewValue(state), nil +} + +func (swapProvider) Rotate(ctx context.Context, inputs map[string]esc.Value) (esc.Value, error) { + state := inputs["state"].Value.(map[string]esc.Value) + newState := esc.NewValue(map[string]esc.Value{ + "a": state["b"], + "b": state["a"], + }) + return newState, nil +} + type testProviders struct { benchDelay time.Duration } @@ -150,6 +180,8 @@ func (tp testProviders) LoadProvider(ctx context.Context, name string) (esc.Prov return testProvider{}, nil case "bench": return benchProvider{delay: tp.benchDelay}, nil + case "swap": + return swapProvider{}, nil } return nil, fmt.Errorf("unknown provider %q", name) } diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go new file mode 100644 index 00000000..2d012013 --- /dev/null +++ b/eval/rotate_example_test.go @@ -0,0 +1,65 @@ +package eval + +import ( + "context" + "github.com/pulumi/esc" + "github.com/stretchr/testify/require" + "testing" +) + +func TestExampleRotate(t *testing.T) { + const def = ` +values: + a: + a: + fn::open: + provider: swap + inputs: + state: + a: a + b: b + b: + - c: + fn::open::swap: + state: + a: + fn::secret: a + b: b +` + env, diags, err := LoadYAMLBytes("", []byte(def)) + require.NoError(t, err) + require.Len(t, diags, 0) + + // rotate the environment + providers := testProviders{} + execContext, err := esc.NewExecContext(nil) + require.NoError(t, err) + open, diags := RotateEnvironment(context.Background(), "", env, rot128{}, providers, &testEnvironments{}, execContext) + require.Len(t, diags, 0) + + // writeback state patches + update, err := esc.ApplyPatches([]byte(def), open.ExecutionContext.Patches) + require.NoError(t, err) + + encryptedYaml, err := EncryptSecrets(context.Background(), "", update, rot128{}) + require.NoError(t, err) + + t.Log(string(encryptedYaml)) + // values: + // a: + // a: + // fn::open: + // provider: swap + // inputs: + // state: + // a: b + // b: a + // b: + // - c: + // fn::open::swap: + // state: + // a: b + // b: + // fn::secret: + // ciphertext: ZXNjeAAAAAHhQRt8TQ== +} diff --git a/patch.go b/patch.go new file mode 100644 index 00000000..3e3dd4f6 --- /dev/null +++ b/patch.go @@ -0,0 +1,77 @@ +package esc + +import ( + "github.com/pulumi/esc/syntax/encoding" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "gopkg.in/yaml.v3" +) + +// Patch represents a value that should be written back to the environment at the given path. +type Patch struct { + DocPath string + Replacement Value +} + +// ApplyPatches applies a set of patches values to an environment definition. +// If patch values contain secret values, they will be wrapped with fn::secret. +func ApplyPatches(source []byte, patches []*Patch) ([]byte, error) { + var doc yaml.Node + if err := yaml.Unmarshal(source, &doc); err != nil { + return nil, err + } + + for _, patch := range patches { + path, err := resource.ParsePropertyPath("values." + patch.DocPath) + if err != nil { + return nil, err + } + + // convert the esc.Value into a yaml node that can be set on the environment + replacement := valueToSecretJSON(patch.Replacement) + bytes, err := yaml.Marshal(replacement) + if err != nil { + return nil, err + } + var yamlValue yaml.Node + if err := yaml.Unmarshal(bytes, &yamlValue); err != nil { + return nil, err + } + yamlValue = *yamlValue.Content[0] + + _, err = encoding.YAMLSyntax{Node: &doc}.Set(nil, path, yamlValue) + if err != nil { + return nil, err + } + } + + return yaml.Marshal(doc.Content[0]) +} + +// valueToSecretJSON converts a Value into a plain-old-JSON value, but secret values are wrapped with fn::secret +func valueToSecretJSON(v Value) any { + ret := func() any { + switch pv := v.Value.(type) { + case []Value: + a := make([]any, len(pv)) + for i, v := range pv { + a[i] = valueToSecretJSON(v) + } + return a + case map[string]Value: + m := make(map[string]any, len(pv)) + for k, v := range pv { + m[k] = valueToSecretJSON(v) + } + return m + default: + return pv + } + }() + // wrap secret values + if v.Secret { + return map[string]any{ + "fn::secret": ret, + } + } + return ret +} From a1f4ec9236ac575058da387dbb133995a40ed595 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:07:18 -0800 Subject: [PATCH 03/30] fn::rotate --- ast/expr.go | 65 ++++++++++++++++++++++++ eval/eval.go | 99 ++++++++++++++++++++++++++++++------- eval/expr.go | 39 +++++++++++++++ eval/rotate_example_test.go | 4 +- provider.go | 1 + 5 files changed, 189 insertions(+), 19 deletions(-) diff --git a/ast/expr.go b/ast/expr.go index 71b7c326..659781c9 100644 --- a/ast/expr.go +++ b/ast/expr.go @@ -434,6 +434,17 @@ func Open(provider string, inputs *ObjectExpr) *OpenExpr { } } +// RotateExpr is a type of OpenExpr that supports a rotate operation. +type RotateExpr struct { + *OpenExpr +} + +func RotateSyntax(node *syntax.ObjectNode, name *StringExpr, args Expr, provider *StringExpr, inputs Expr) *RotateExpr { + return &RotateExpr{ + OpenExpr: OpenSyntax(node, name, args, provider, inputs), + } +} + // ToJSON returns the underlying structure as a json string. type ToJSONExpr struct { builtinNode @@ -607,6 +618,8 @@ func tryParseFunction(node *syntax.ObjectNode) (Expr, syntax.Diagnostics, bool) parse = parseJoin case "fn::open": parse = parseOpen + case "fn::rotate": + parse = parseRotate case "fn::secret": parse = parseSecret case "fn::toBase64": @@ -620,6 +633,10 @@ func tryParseFunction(node *syntax.ObjectNode) (Expr, syntax.Diagnostics, bool) parse = parseShortOpen break } + if strings.HasPrefix(kvp.Key.Value(), "fn::rotate::") { + parse = parseShortRotate + break + } if strings.HasPrefix(strings.ToLower(kvp.Key.Value()), "fn::") { diags = append(diags, syntax.Error(kvp.Key.Syntax().Range(), @@ -696,6 +713,54 @@ func parseShortOpen(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, return OpenSyntax(node, name, args, provider, args), nil } +func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { + obj, ok := args.(*ObjectExpr) + if !ok { + diags := syntax.Diagnostics{ExprError(args, "the argument to fn::rotate must be an object containing 'provider' and 'inputs'")} + return RotateSyntax(node, name, args, nil, nil), diags + } + + var providerExpr, inputs Expr + var diags syntax.Diagnostics + + for i := 0; i < len(obj.Entries); i++ { + kvp := obj.Entries[i] + key := kvp.Key + switch key.GetValue() { + case "provider": + providerExpr = kvp.Value + case "inputs": + inputs = kvp.Value + } + } + + provider, ok := providerExpr.(*StringExpr) + if !ok { + if providerExpr == nil { + diags.Extend(ExprError(obj, "missing provider name ('provider')")) + } else { + diags.Extend(ExprError(providerExpr, "provider name must be a string literal")) + } + } + + if inputs == nil { + diags.Extend(ExprError(obj, "missing provider inputs ('inputs')")) + } + + return RotateSyntax(node, name, obj, provider, inputs), diags +} + +func parseShortRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { + kvp := node.Index(0) + provider := StringSyntaxValue(name.Syntax().(*syntax.StringNode), strings.TrimPrefix(kvp.Key.Value(), "fn::rotate::")) + if args == nil { + diags := syntax.Diagnostics{ExprError(name, "missing provider inputs")} + return RotateSyntax(node, name, args, provider, nil), diags + } + + return RotateSyntax(node, name, args, provider, args), nil +} + func parseJoin(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { list, ok := args.(*ArrayExpr) if !ok || len(list.Elements) != 2 { diff --git a/eval/eval.go b/eval/eval.go index a30a7f4f..1178084d 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -323,6 +323,14 @@ func declare[Expr exprNode](e *evalContext, path string, x Expr, base *value) *e inputSchema: schema.Always().Schema(), } return newExpr(path, repr, schema.Always().Schema(), base) + case *ast.RotateExpr: + repr := &rotateExpr{ + node: x, + provider: declare(e, "", x.Provider, nil), + inputs: declare(e, "", x.Inputs, nil), + inputSchema: schema.Always().Schema(), + } + return newExpr(path, repr, schema.Always().Schema(), base) case *ast.SecretExpr: if x.Plaintext != nil { repr := &secretExpr{node: x, plaintext: declare(e, "", x.Plaintext, nil)} @@ -548,6 +556,8 @@ func (e *evalContext) evaluateExpr(x *expr) *value { val = e.evaluateBuiltinJoin(x, repr) case *openExpr: val = e.evaluateBuiltinOpen(x, repr) + case *rotateExpr: + val = e.evaluateBuiltinRotate(x, repr) case *secretExpr: val = e.evaluateBuiltinSecret(x, repr) case *toBase64Expr: @@ -945,27 +955,82 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { return v } + output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), e.execContext) + if err != nil { + e.errorf(repr.syntax(), "%s", err.Error()) + v.unknown = true + return v + } + return unexport(output, x) +} + +func loadRotator(ctx context.Context, providers ProviderLoader, name string) (esc.Rotator, error) { + provider, err := providers.LoadProvider(ctx, name) + if err != nil { + return nil, err + } + rotator, ok := provider.(esc.Rotator) + if !ok { + return nil, fmt.Errorf("provider is not a rotator") + } + return rotator, nil +} + +// evaluateBuiltinOpen evaluates a call to the fn::rotate builtin. +func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { + v := &value{def: x} + + // Can happen if there are parse errors. + if repr.node.Provider == nil { + v.schema = schema.Always() + v.unknown = true + return v + } + + provider, err := loadRotator(e.ctx, e.providers, repr.node.Provider.GetValue()) + if err != nil { + e.errorf(repr.syntax(), "%v", err) + } else { + inputSchema, outputSchema := provider.Schema() + if err := inputSchema.Compile(); err != nil { + e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) + } else { + repr.inputSchema = inputSchema + } + if err := outputSchema.Compile(); err != nil { + e.errorf(repr.syntax(), "internal error: invalid schema (%v)", err) + } else { + x.schema = outputSchema + } + } + v.schema = x.schema + + inputs, ok := e.evaluateTypedExpr(repr.inputs, repr.inputSchema) + if !ok || inputs.containsUnknowns() || e.validating || err != nil { + v.unknown = true + return v + } + // if rotating, invoke prior to open if e.rotating { - if rotator, ok := provider.(esc.Rotator); ok { - newState, err := rotator.Rotate(e.ctx, inputs.export("").Value.(map[string]esc.Value)) - if err != nil { - e.errorf(repr.syntax(), "rotate: %s", err.Error()) - v.unknown = true - return v - } - - // path to provider arguments differs for long vs short form - inputsPath := repr.node.Name().GetValue() - if inputsPath == "fn::open" { - inputsPath += ".inputs" - } + newState, err := provider.Rotate(e.ctx, inputs.export("").Value.(map[string]esc.Value)) + if err != nil { + e.errorf(repr.syntax(), "rotate: %s", err.Error()) + v.unknown = true + return v + } - e.patchOutputs = append(e.patchOutputs, &esc.Patch{ - DocPath: x.path + "." + inputsPath + ".state", - Replacement: newState, - }) + // path to provider arguments differs for long vs short form + inputsPath := repr.node.Name().GetValue() + if inputsPath == "fn::rotate" { + inputsPath += ".inputs" } + + e.patchOutputs = append(e.patchOutputs, &esc.Patch{ + // rotation output is written back to the fn's `state` input + DocPath: x.path + "." + inputsPath + ".state", + Replacement: newState, + }) } output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), e.execContext) diff --git a/eval/expr.go b/eval/expr.go index f13b1148..d2d91099 100644 --- a/eval/expr.go +++ b/eval/expr.go @@ -204,6 +204,31 @@ func (x *expr) export(environment string) esc.Expr { Arg: repr.inputs.export(environment), } } + case *rotateExpr: + name := repr.node.Name().Value + if name == "fn::rotate" { + ex.Builtin = &esc.BuiltinExpr{ + Name: name, + NameRange: convertRange(repr.node.Name().Syntax().Syntax().Range(), environment), + ArgSchema: schema.Record(schema.SchemaMap{ + "provider": schema.String().Schema(), + "inputs": repr.inputSchema, + }).Schema(), + Arg: esc.Expr{ + Object: map[string]esc.Expr{ + "provider": repr.provider.export(environment), + "inputs": repr.inputs.export(environment), + }, + }, + } + } else { + ex.Builtin = &esc.BuiltinExpr{ + Name: name, + NameRange: convertRange(repr.node.Name().Syntax().Syntax().Range(), environment), + ArgSchema: repr.inputSchema, + Arg: repr.inputs.export(environment), + } + } case *secretExpr: var arg esc.Expr if repr.plaintext != nil { @@ -371,6 +396,20 @@ func (x *openExpr) syntax() ast.Expr { return x.node } +// rotateExpr represents a call to the fn::rotate builtin. +type rotateExpr struct { + node *ast.RotateExpr + + provider *expr + inputs *expr + + inputSchema *schema.Schema +} + +func (x *rotateExpr) syntax() ast.Expr { + return x.node +} + // toJSONExpr represents a call to the fn::toJSON builtin. type toJSONExpr struct { node *ast.ToJSONExpr diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index 2d012013..a74d5ec9 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -12,7 +12,7 @@ func TestExampleRotate(t *testing.T) { values: a: a: - fn::open: + fn::rotate: provider: swap inputs: state: @@ -20,7 +20,7 @@ values: b: b b: - c: - fn::open::swap: + fn::rotate::swap: state: a: fn::secret: a diff --git a/provider.go b/provider.go index d72b4bca..65552af1 100644 --- a/provider.go +++ b/provider.go @@ -31,6 +31,7 @@ type Provider interface { } type Rotator interface { + Provider // Rotate rotates the provider's secret. // The result represents the new state which will be written back into the environment under the `state` key of the provider's inputs. // Any secret values in the result will be wrapped by fn::secret in the written result. From 4fa0993d4b634d0e0b5ad3f59508dddb4036ce44 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:55:24 -0800 Subject: [PATCH 04/30] rotate scoped to specific paths --- environment.go | 3 --- eval/eval.go | 42 +++++++++++++++++++++---------------- eval/rotate_example_test.go | 4 ++-- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/environment.go b/environment.go index df3b302e..551314ab 100644 --- a/environment.go +++ b/environment.go @@ -160,9 +160,6 @@ type EvaluatedExecutionContext struct { // Schema contains the schema for Properties. Schema *schema.Schema `json:"schema,omitempty"` - - // Patches contains updated values that should be written back into the environment definition. - Patches []*Patch } // An Environment contains the result of evaluating an environment definition. diff --git a/eval/eval.go b/eval/eval.go index 1178084d..0d972717 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -83,7 +83,8 @@ func EvalEnvironment( environments EnvironmentLoader, execContext *esc.ExecContext, ) (*esc.Environment, syntax.Diagnostics) { - return evalEnvironment(ctx, false, false, name, env, decrypter, providers, environments, execContext, true) + opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, nil) + return opened, diags } // CheckEnvironment symbolically evaluates the given environment. Calls to fn::open are not invoked, and instead @@ -98,11 +99,12 @@ func CheckEnvironment( execContext *esc.ExecContext, showSecrets bool, ) (*esc.Environment, syntax.Diagnostics) { - return evalEnvironment(ctx, true, false, name, env, decrypter, providers, environments, execContext, showSecrets) + checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, environments, execContext, showSecrets, nil) + return checked, diags } // RotateEnvironment evaluates the given environment and invokes provider rotate methods. -// Updated rotation state will output via execution context patches, and is expected to be written back to the environment definition by the caller. +// The updated rotation state is returned a set of patches to be written back to the environment. func RotateEnvironment( ctx context.Context, name string, @@ -111,15 +113,19 @@ func RotateEnvironment( providers ProviderLoader, environments EnvironmentLoader, execContext *esc.ExecContext, -) (*esc.Environment, syntax.Diagnostics) { - return evalEnvironment(ctx, false, true, name, env, decrypter, providers, environments, execContext, true) + paths []string, +) (*esc.Environment, []*esc.Patch, syntax.Diagnostics) { + rotatePaths := map[string]bool{} + for _, path := range paths { + rotatePaths[path] = true + } + return evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, rotatePaths) } // evalEnvironment evaluates an environment and exports the result of evaluation. func evalEnvironment( ctx context.Context, validating bool, - rotating bool, name string, env *ast.EnvironmentDecl, decrypter Decrypter, @@ -127,12 +133,13 @@ func evalEnvironment( envs EnvironmentLoader, execContext *esc.ExecContext, showSecrets bool, -) (*esc.Environment, syntax.Diagnostics) { + rotatePaths map[string]bool, +) (*esc.Environment, []*esc.Patch, syntax.Diagnostics) { if env == nil || (len(env.Values.GetEntries()) == 0 && len(env.Imports.GetElements()) == 0) { - return nil, nil + return nil, nil, nil } - ec := newEvalContext(ctx, validating, rotating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets) + ec := newEvalContext(ctx, validating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) v, diags := ec.evaluate() s := schema.Never().Schema() @@ -147,7 +154,6 @@ func evalEnvironment( executionContext := &esc.EvaluatedExecutionContext{ Properties: ec.myContext.export(name).Value.(map[string]esc.Value), Schema: ec.myContext.schema, - Patches: ec.patchOutputs, } return &esc.Environment{ @@ -155,7 +161,7 @@ func evalEnvironment( Properties: v.export(name).Value.(map[string]esc.Value), Schema: s, ExecutionContext: executionContext, - }, diags + }, ec.patchOutputs, diags } type imported struct { @@ -181,8 +187,8 @@ type evalContext struct { root *expr // the root expression base *value // the base value - rotating bool // true if providers should be rotated - patchOutputs []*esc.Patch // updated rotation state to be written back to the environment definition + rotatePaths map[string]bool // when non-nil, specifies providers to rotate. if empty, the full environment is rotated. + patchOutputs []*esc.Patch // updated rotation state to be written back to the environment definition diags syntax.Diagnostics // diagnostics generated during evaluation } @@ -190,7 +196,6 @@ type evalContext struct { func newEvalContext( ctx context.Context, validating bool, - rotating bool, name string, env *ast.EnvironmentDecl, decrypter Decrypter, @@ -199,11 +204,11 @@ func newEvalContext( imports map[string]*imported, execContext *esc.ExecContext, showSecrets bool, + rotatePaths map[string]bool, ) *evalContext { return &evalContext{ ctx: ctx, validating: validating, - rotating: rotating, showSecrets: showSecrets, name: name, env: env, @@ -212,6 +217,7 @@ func newEvalContext( environments: environments, imports: imports, execContext: execContext.CopyForEnv(name), + rotatePaths: rotatePaths, } } @@ -492,7 +498,7 @@ func (e *evalContext) evaluateImport(myImports map[string]*value, decl *ast.Impo } // only rotate root environment - imp := newEvalContext(e.ctx, e.validating, false, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets) + imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets, nil) v, diags := imp.evaluate() e.diags.Extend(diags...) @@ -1006,13 +1012,13 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { v.schema = x.schema inputs, ok := e.evaluateTypedExpr(repr.inputs, repr.inputSchema) - if !ok || inputs.containsUnknowns() || e.validating || err != nil { + if !ok || inputs.containsUnknowns() || e.validating || e.rotatePaths == nil || err != nil { v.unknown = true return v } // if rotating, invoke prior to open - if e.rotating { + if len(e.rotatePaths) == 0 || e.rotatePaths[x.path] { newState, err := provider.Rotate(e.ctx, inputs.export("").Value.(map[string]esc.Value)) if err != nil { e.errorf(repr.syntax(), "rotate: %s", err.Error()) diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index a74d5ec9..48711556 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -34,11 +34,11 @@ values: providers := testProviders{} execContext, err := esc.NewExecContext(nil) require.NoError(t, err) - open, diags := RotateEnvironment(context.Background(), "", env, rot128{}, providers, &testEnvironments{}, execContext) + _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, providers, &testEnvironments{}, execContext, nil) require.Len(t, diags, 0) // writeback state patches - update, err := esc.ApplyPatches([]byte(def), open.ExecutionContext.Patches) + update, err := esc.ApplyPatches([]byte(def), patches) require.NoError(t, err) encryptedYaml, err := EncryptSecrets(context.Background(), "", update, rot128{}) From 4e22ea22f6cfbae8898e912661873ee61164a35f Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:58:47 -0800 Subject: [PATCH 05/30] move patch handling to eval pkg --- eval/eval.go | 8 ++++---- patch.go => eval/patch.go | 11 ++++++----- eval/rotate_example_test.go | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) rename patch.go => eval/patch.go (92%) diff --git a/eval/eval.go b/eval/eval.go index 0d972717..81ae464f 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -114,7 +114,7 @@ func RotateEnvironment( environments EnvironmentLoader, execContext *esc.ExecContext, paths []string, -) (*esc.Environment, []*esc.Patch, syntax.Diagnostics) { +) (*esc.Environment, []*Patch, syntax.Diagnostics) { rotatePaths := map[string]bool{} for _, path := range paths { rotatePaths[path] = true @@ -134,7 +134,7 @@ func evalEnvironment( execContext *esc.ExecContext, showSecrets bool, rotatePaths map[string]bool, -) (*esc.Environment, []*esc.Patch, syntax.Diagnostics) { +) (*esc.Environment, []*Patch, syntax.Diagnostics) { if env == nil || (len(env.Values.GetEntries()) == 0 && len(env.Imports.GetElements()) == 0) { return nil, nil, nil } @@ -188,7 +188,7 @@ type evalContext struct { base *value // the base value rotatePaths map[string]bool // when non-nil, specifies providers to rotate. if empty, the full environment is rotated. - patchOutputs []*esc.Patch // updated rotation state to be written back to the environment definition + patchOutputs []*Patch // updated rotation state to be written back to the environment definition diags syntax.Diagnostics // diagnostics generated during evaluation } @@ -1032,7 +1032,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { inputsPath += ".inputs" } - e.patchOutputs = append(e.patchOutputs, &esc.Patch{ + e.patchOutputs = append(e.patchOutputs, &Patch{ // rotation output is written back to the fn's `state` input DocPath: x.path + "." + inputsPath + ".state", Replacement: newState, diff --git a/patch.go b/eval/patch.go similarity index 92% rename from patch.go rename to eval/patch.go index 3e3dd4f6..a299214e 100644 --- a/patch.go +++ b/eval/patch.go @@ -1,6 +1,7 @@ -package esc +package eval import ( + "github.com/pulumi/esc" "github.com/pulumi/esc/syntax/encoding" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "gopkg.in/yaml.v3" @@ -9,7 +10,7 @@ import ( // Patch represents a value that should be written back to the environment at the given path. type Patch struct { DocPath string - Replacement Value + Replacement esc.Value } // ApplyPatches applies a set of patches values to an environment definition. @@ -48,16 +49,16 @@ func ApplyPatches(source []byte, patches []*Patch) ([]byte, error) { } // valueToSecretJSON converts a Value into a plain-old-JSON value, but secret values are wrapped with fn::secret -func valueToSecretJSON(v Value) any { +func valueToSecretJSON(v esc.Value) any { ret := func() any { switch pv := v.Value.(type) { - case []Value: + case []esc.Value: a := make([]any, len(pv)) for i, v := range pv { a[i] = valueToSecretJSON(v) } return a - case map[string]Value: + case map[string]esc.Value: m := make(map[string]any, len(pv)) for k, v := range pv { m[k] = valueToSecretJSON(v) diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index 48711556..a90cf503 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -38,7 +38,7 @@ values: require.Len(t, diags, 0) // writeback state patches - update, err := esc.ApplyPatches([]byte(def), patches) + update, err := ApplyPatches([]byte(def), patches) require.NoError(t, err) encryptedYaml, err := EncryptSecrets(context.Background(), "", update, rot128{}) From 65b0277c7db0d5d93a624aef63fe30af07f4a9d2 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:52:06 -0800 Subject: [PATCH 06/30] separate rotator inputs and state --- ast/expr.go | 71 ++++++++++++++++++++++++++++++------ eval/eval.go | 73 ++++++++++++++++++++++--------------- eval/eval_test.go | 33 ++++++++++------- eval/expr.go | 16 +++++++- eval/rotate_example_test.go | 11 +++--- provider.go | 15 +++++--- 6 files changed, 153 insertions(+), 66 deletions(-) diff --git a/ast/expr.go b/ast/expr.go index 659781c9..4a5dc590 100644 --- a/ast/expr.go +++ b/ast/expr.go @@ -18,6 +18,7 @@ import ( "encoding/json" "fmt" "reflect" + "slices" "strings" "github.com/hashicorp/hcl/v2" @@ -436,12 +437,36 @@ func Open(provider string, inputs *ObjectExpr) *OpenExpr { // RotateExpr is a type of OpenExpr that supports a rotate operation. type RotateExpr struct { - *OpenExpr + builtinNode + + Provider *StringExpr + Inputs Expr + State *ObjectExpr } -func RotateSyntax(node *syntax.ObjectNode, name *StringExpr, args Expr, provider *StringExpr, inputs Expr) *RotateExpr { +func RotateSyntax(node *syntax.ObjectNode, name *StringExpr, args Expr, provider *StringExpr, inputs Expr, state *ObjectExpr) *RotateExpr { return &RotateExpr{ - OpenExpr: OpenSyntax(node, name, args, provider, inputs), + builtinNode: builtin(node, name, args), + Provider: provider, + Inputs: inputs, + State: state, + } +} + +func Rotate(provider string, inputs, state *ObjectExpr) *RotateExpr { + name, providerX := String("fn::rotate"), String(provider) + + entries := []ObjectProperty{ + {Key: String("provider"), Value: providerX}, + {Key: String("inputs"), Value: inputs}, + {Key: String("state"), Value: state}, + } + + return &RotateExpr{ + builtinNode: builtin(nil, name, Object(entries...)), + Provider: providerX, + Inputs: inputs, + State: state, } } @@ -716,11 +741,11 @@ func parseShortOpen(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { obj, ok := args.(*ObjectExpr) if !ok { - diags := syntax.Diagnostics{ExprError(args, "the argument to fn::rotate must be an object containing 'provider' and 'inputs'")} - return RotateSyntax(node, name, args, nil, nil), diags + diags := syntax.Diagnostics{ExprError(args, "the argument to fn::rotate must be an object containing 'provider', 'inputs' and 'state'")} + return RotateSyntax(node, name, args, nil, nil, nil), diags } - var providerExpr, inputs Expr + var providerExpr, inputs, stateExpr Expr var diags syntax.Diagnostics for i := 0; i < len(obj.Entries); i++ { @@ -731,6 +756,8 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy providerExpr = kvp.Value case "inputs": inputs = kvp.Value + case "state": + stateExpr = kvp.Value } } @@ -747,18 +774,40 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy diags.Extend(ExprError(obj, "missing provider inputs ('inputs')")) } - return RotateSyntax(node, name, obj, provider, inputs), diags + state, ok := stateExpr.(*ObjectExpr) + if !ok && state != nil { + diags.Extend(ExprError(stateExpr, "rotation state must be an object literal")) + } + + return RotateSyntax(node, name, obj, provider, inputs, state), diags } func parseShortRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { kvp := node.Index(0) provider := StringSyntaxValue(name.Syntax().(*syntax.StringNode), strings.TrimPrefix(kvp.Key.Value(), "fn::rotate::")) - if args == nil { - diags := syntax.Diagnostics{ExprError(name, "missing provider inputs")} - return RotateSyntax(node, name, args, provider, nil), diags + + inputs, ok := args.(*ObjectExpr) + if !ok { + diags := syntax.Diagnostics{ExprError(args, "provider inputs must be an object")} + return RotateSyntax(node, name, args, nil, nil, nil), diags + } + + // hoist 'state' key out of inputs + var stateExpr Expr + if i := slices.IndexFunc(inputs.Entries, func(kvp ObjectProperty) bool { + return kvp.Key.GetValue() == "state" + }); i != -1 { + stateExpr = inputs.Entries[i].Value + inputs.Entries = slices.Delete(inputs.Entries, i, i+1) + } + + state, ok := stateExpr.(*ObjectExpr) + if !ok && state != nil { + diags := syntax.Diagnostics{ExprError(stateExpr, "rotation state must be an object literal")} + return RotateSyntax(node, name, args, nil, nil, nil), diags } - return RotateSyntax(node, name, args, provider, args), nil + return RotateSyntax(node, name, args, provider, inputs, state), nil } func parseJoin(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { diff --git a/eval/eval.go b/eval/eval.go index 81ae464f..707f95ca 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -39,6 +39,12 @@ type ProviderLoader interface { LoadProvider(ctx context.Context, name string) (esc.Provider, error) } +// A RotatorLoader provides the environment evaluator the capability to load rotators. +type RotatorLoader interface { + // LoadRotator loads the rotator with the given name. + LoadRotator(ctx context.Context, name string) (esc.Rotator, error) +} + // An EnvironmentLoader provides the environment evaluator the capability to load imported environment definitions. type EnvironmentLoader interface { // LoadEnvironment loads the definition for the environment with the given name. @@ -83,7 +89,7 @@ func EvalEnvironment( environments EnvironmentLoader, execContext *esc.ExecContext, ) (*esc.Environment, syntax.Diagnostics) { - opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, nil) + opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, nil, environments, execContext, true, nil) return opened, diags } @@ -99,7 +105,7 @@ func CheckEnvironment( execContext *esc.ExecContext, showSecrets bool, ) (*esc.Environment, syntax.Diagnostics) { - checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, environments, execContext, showSecrets, nil) + checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, nil, environments, execContext, showSecrets, nil) return checked, diags } @@ -111,6 +117,7 @@ func RotateEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, + rotators RotatorLoader, environments EnvironmentLoader, execContext *esc.ExecContext, paths []string, @@ -119,7 +126,7 @@ func RotateEnvironment( for _, path := range paths { rotatePaths[path] = true } - return evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, rotatePaths) + return evalEnvironment(ctx, false, name, env, decrypter, providers, rotators, environments, execContext, true, rotatePaths) } // evalEnvironment evaluates an environment and exports the result of evaluation. @@ -130,6 +137,7 @@ func evalEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, + rotators RotatorLoader, envs EnvironmentLoader, execContext *esc.ExecContext, showSecrets bool, @@ -139,7 +147,7 @@ func evalEnvironment( return nil, nil, nil } - ec := newEvalContext(ctx, validating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) + ec := newEvalContext(ctx, validating, name, env, decrypter, providers, rotators, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) v, diags := ec.evaluate() s := schema.Never().Schema() @@ -178,6 +186,7 @@ type evalContext struct { env *ast.EnvironmentDecl // the root of the environment AST decrypter Decrypter // the decrypter to use for the environment providers ProviderLoader // the provider loader to use + rotators RotatorLoader // the rotator loader to use environments EnvironmentLoader // the environment loader to use imports map[string]*imported // the shared set of imported environments execContext *esc.ExecContext // evaluation context used for interpolation @@ -200,6 +209,7 @@ func newEvalContext( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, + rotators RotatorLoader, environments EnvironmentLoader, imports map[string]*imported, execContext *esc.ExecContext, @@ -214,6 +224,7 @@ func newEvalContext( env: env, decrypter: decrypter, providers: providers, + rotators: rotators, environments: environments, imports: imports, execContext: execContext.CopyForEnv(name), @@ -334,7 +345,9 @@ func declare[Expr exprNode](e *evalContext, path string, x Expr, base *value) *e node: x, provider: declare(e, "", x.Provider, nil), inputs: declare(e, "", x.Inputs, nil), + state: declare(e, "", x.State, nil), inputSchema: schema.Always().Schema(), + stateSchema: schema.Always().Schema(), } return newExpr(path, repr, schema.Always().Schema(), base) case *ast.SecretExpr: @@ -498,7 +511,7 @@ func (e *evalContext) evaluateImport(myImports map[string]*value, decl *ast.Impo } // only rotate root environment - imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets, nil) + imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, nil, e.environments, e.imports, e.execContext, e.showSecrets, nil) v, diags := imp.evaluate() e.diags.Extend(diags...) @@ -970,18 +983,6 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { return unexport(output, x) } -func loadRotator(ctx context.Context, providers ProviderLoader, name string) (esc.Rotator, error) { - provider, err := providers.LoadProvider(ctx, name) - if err != nil { - return nil, err - } - rotator, ok := provider.(esc.Rotator) - if !ok { - return nil, fmt.Errorf("provider is not a rotator") - } - return rotator, nil -} - // evaluateBuiltinOpen evaluates a call to the fn::rotate builtin. func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { v := &value{def: x} @@ -993,16 +994,21 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { return v } - provider, err := loadRotator(e.ctx, e.providers, repr.node.Provider.GetValue()) + rotator, err := e.rotators.LoadRotator(e.ctx, repr.node.Provider.GetValue()) if err != nil { e.errorf(repr.syntax(), "%v", err) } else { - inputSchema, outputSchema := provider.Schema() + inputSchema, stateSchema, outputSchema := rotator.Schema() if err := inputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) } else { repr.inputSchema = inputSchema } + if err := stateSchema.Compile(); err != nil { + e.errorf(repr.syntax(), "internal error: invalid state schema (%v)", err) + } else { + repr.stateSchema = stateSchema + } if err := outputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid schema (%v)", err) } else { @@ -1011,35 +1017,44 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { } v.schema = x.schema - inputs, ok := e.evaluateTypedExpr(repr.inputs, repr.inputSchema) - if !ok || inputs.containsUnknowns() || e.validating || e.rotatePaths == nil || err != nil { + inputs, inputsOK := e.evaluateTypedExpr(repr.inputs, repr.inputSchema) + state, stateOK := e.evaluateTypedExpr(repr.state, repr.stateSchema) + if !inputsOK || inputs.containsUnknowns() || !stateOK || state.containsUnknowns() || e.validating || e.rotatePaths == nil || err != nil { v.unknown = true return v } // if rotating, invoke prior to open if len(e.rotatePaths) == 0 || e.rotatePaths[x.path] { - newState, err := provider.Rotate(e.ctx, inputs.export("").Value.(map[string]esc.Value)) + newState, err := rotator.Rotate( + e.ctx, + inputs.export("").Value.(map[string]esc.Value), + state.export("").Value.(map[string]esc.Value), + ) if err != nil { e.errorf(repr.syntax(), "rotate: %s", err.Error()) v.unknown = true return v } - // path to provider arguments differs for long vs short form - inputsPath := repr.node.Name().GetValue() - if inputsPath == "fn::rotate" { - inputsPath += ".inputs" - } + // todo: validate newState conforms to state schema e.patchOutputs = append(e.patchOutputs, &Patch{ // rotation output is written back to the fn's `state` input - DocPath: x.path + "." + inputsPath + ".state", + DocPath: x.path + "." + repr.node.Name().GetValue() + ".state", Replacement: newState, }) + + // pass the updated state to open, as if it were already persisted + state = unexport(newState, x) } - output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), e.execContext) + output, err := rotator.Open( + e.ctx, + inputs.export("").Value.(map[string]esc.Value), + state.export("").Value.(map[string]esc.Value), + e.execContext, + ) if err != nil { e.errorf(repr.syntax(), "%s", err.Error()) v.unknown = true diff --git a/eval/eval_test.go b/eval/eval_test.go index 2894e815..c895874c 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -136,29 +136,26 @@ func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, conte return esc.NewValue(inputs), nil } -type swapProvider struct{} +type swapRotator struct{} -func (swapProvider) Schema() (*schema.Schema, *schema.Schema) { - inputSchema := schema.Record(schema.BuilderMap{ - "state": schema.Record(schema.BuilderMap{ - "a": schema.String(), - "b": schema.String(), - }), +func (swapRotator) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { + inputSchema := schema.Always() + stateSchema := schema.Record(schema.BuilderMap{ + "a": schema.String(), + "b": schema.String(), }).Schema() outputSchema := schema.Record(schema.BuilderMap{ "a": schema.String(), "b": schema.String(), }).Schema() - return inputSchema, outputSchema + return inputSchema, stateSchema, outputSchema } -func (swapProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { - state := inputs["state"].Value.(map[string]esc.Value) +func (swapRotator) Open(ctx context.Context, inputs, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(state), nil } -func (swapProvider) Rotate(ctx context.Context, inputs map[string]esc.Value) (esc.Value, error) { - state := inputs["state"].Value.(map[string]esc.Value) +func (swapRotator) Rotate(ctx context.Context, inputs, state map[string]esc.Value) (esc.Value, error) { newState := esc.NewValue(map[string]esc.Value{ "a": state["b"], "b": state["a"], @@ -180,12 +177,20 @@ func (tp testProviders) LoadProvider(ctx context.Context, name string) (esc.Prov return testProvider{}, nil case "bench": return benchProvider{delay: tp.benchDelay}, nil - case "swap": - return swapProvider{}, nil } return nil, fmt.Errorf("unknown provider %q", name) } +type testRotators struct{} + +func (testRotators) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { + switch name { + case "swap": + return swapRotator{}, nil + } + return nil, fmt.Errorf("unknown rotator %q", name) +} + type testEnvironments struct { root string } diff --git a/eval/expr.go b/eval/expr.go index d2d91099..b793eb6d 100644 --- a/eval/expr.go +++ b/eval/expr.go @@ -16,6 +16,7 @@ package eval import ( "fmt" + "maps" "github.com/hashicorp/hcl/v2" "github.com/pulumi/esc" @@ -213,20 +214,29 @@ func (x *expr) export(environment string) esc.Expr { ArgSchema: schema.Record(schema.SchemaMap{ "provider": schema.String().Schema(), "inputs": repr.inputSchema, + "state": repr.stateSchema, }).Schema(), Arg: esc.Expr{ Object: map[string]esc.Expr{ "provider": repr.provider.export(environment), "inputs": repr.inputs.export(environment), + "state": repr.state.export(environment), }, }, } } else { + argMap := schema.SchemaMap{} + maps.Copy(argMap, repr.inputSchema.Properties) + argMap["state"] = repr.stateSchema + + arg := repr.inputs.export(environment) + arg.Object["state"] = repr.state.export(environment) + ex.Builtin = &esc.BuiltinExpr{ Name: name, NameRange: convertRange(repr.node.Name().Syntax().Syntax().Range(), environment), - ArgSchema: repr.inputSchema, - Arg: repr.inputs.export(environment), + ArgSchema: schema.Record(argMap).Schema(), + Arg: arg, } } case *secretExpr: @@ -402,8 +412,10 @@ type rotateExpr struct { provider *expr inputs *expr + state *expr inputSchema *schema.Schema + stateSchema *schema.Schema } func (x *rotateExpr) syntax() ast.Expr { diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index a90cf503..5c04bf59 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -15,12 +15,14 @@ values: fn::rotate: provider: swap inputs: - state: - a: a - b: b + foo: bar + state: + a: a + b: b b: - c: fn::rotate::swap: + foo: bar state: a: fn::secret: a @@ -31,10 +33,9 @@ values: require.Len(t, diags, 0) // rotate the environment - providers := testProviders{} execContext, err := esc.NewExecContext(nil) require.NoError(t, err) - _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, providers, &testEnvironments{}, execContext, nil) + _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, testProviders{}, testRotators{}, &testEnvironments{}, execContext, nil) require.Len(t, diags, 0) // writeback state patches diff --git a/provider.go b/provider.go index 65552af1..8265c24c 100644 --- a/provider.go +++ b/provider.go @@ -30,10 +30,15 @@ type Provider interface { Open(ctx context.Context, inputs map[string]Value, executionContext EnvExecContext) (Value, error) } +// A Rotator enables environments to rotate a secret. +// Rotators persist internal state by writing back to the environment. type Rotator interface { - Provider - // Rotate rotates the provider's secret. - // The result represents the new state which will be written back into the environment under the `state` key of the provider's inputs. - // Any secret values in the result will be wrapped by fn::secret in the written result. - Rotate(ctx context.Context, inputs map[string]Value) (Value, error) + // Schema returns the provider's input, state, and output schemata. + Schema() (inputs, state, outputs *schema.Schema) + + // Open retrieves the provider's secrets. + Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error) + + // Rotate rotates the provider's secret, and returns the rotator's new state to be persisted. + Rotate(ctx context.Context, inputs, state map[string]Value) (Value, error) } From 54d997762b1dab1363fb074bc6127559b7b6c28d Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:51:46 -0800 Subject: [PATCH 07/30] need to add RotatorLoaders to Check and Open too --- analysis/common_test.go | 6 ++++++ analysis/describe_test.go | 4 ++-- analysis/traversal_test.go | 2 +- cmd/esc/cli/cli_test.go | 12 ++++++++++-- eval/eval.go | 6 ++++-- eval/eval_test.go | 10 +++++----- 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/analysis/common_test.go b/analysis/common_test.go index c21d8c13..e987a689 100644 --- a/analysis/common_test.go +++ b/analysis/common_test.go @@ -71,6 +71,12 @@ func (testProviders) LoadProvider(ctx context.Context, name string) (esc.Provide return nil, fmt.Errorf("unknown provider %q", name) } +type testRotators struct{} + +func (testRotators) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { + return nil, fmt.Errorf("unknown provider %q", name) +} + type testEnvironments struct{} func (testEnvironments) LoadEnvironment(ctx context.Context, name string) ([]byte, eval.Decrypter, error) { diff --git a/analysis/describe_test.go b/analysis/describe_test.go index 35789693..c61ecefb 100644 --- a/analysis/describe_test.go +++ b/analysis/describe_test.go @@ -34,7 +34,7 @@ func TestDescribe(t *testing.T) { execContext, err := esc.NewExecContext(make(map[string]esc.Value)) require.NoError(t, err) - env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testEnvironments{}, execContext, false) + env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testRotators{}, testEnvironments{}, execContext, false) require.Empty(t, diags) analysis := New(*env, map[string]*schema.Schema{"test": testProviderSchema}) @@ -108,7 +108,7 @@ func TestDescribeOpen(t *testing.T) { execContext, err := esc.NewExecContext(make(map[string]esc.Value)) require.NoError(t, err) - env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testEnvironments{}, execContext, false) + env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testRotators{}, testEnvironments{}, execContext, false) require.Empty(t, diags) analysis := New(*env, map[string]*schema.Schema{"test": testProviderSchema}) diff --git a/analysis/traversal_test.go b/analysis/traversal_test.go index 60cc79c1..41a27dfe 100644 --- a/analysis/traversal_test.go +++ b/analysis/traversal_test.go @@ -33,7 +33,7 @@ func TestExpressionAt(t *testing.T) { execContext, err := esc.NewExecContext(make(map[string]esc.Value)) require.NoError(t, err) - env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testEnvironments{}, execContext, false) + env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testRotators{}, testEnvironments{}, execContext, false) require.Empty(t, diags) analysis := New(*env, map[string]*schema.Schema{"test": testProviderSchema}) diff --git a/cmd/esc/cli/cli_test.go b/cmd/esc/cli/cli_test.go index 87674f71..2014e69b 100644 --- a/cmd/esc/cli/cli_test.go +++ b/cmd/esc/cli/cli_test.go @@ -215,6 +215,12 @@ func (testProviders) LoadProvider(ctx context.Context, name string) (esc.Provide return nil, fmt.Errorf("unknown provider %q", name) } +type testRotators struct{} + +func (testRotators) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { + return nil, fmt.Errorf("unknown rotator %q", name) +} + type rot128 struct{} func (rot128) Encrypt(_ context.Context, plaintext []byte) ([]byte, error) { @@ -395,6 +401,7 @@ func (c *testPulumiClient) checkEnvironment(ctx context.Context, orgName, envNam } providers := &testProviders{} + rotators := &testRotators{} envLoader := &testEnvironments{orgName: orgName, environments: c.environments} execContext, err := esc.NewExecContext(make(map[string]esc.Value)) @@ -407,7 +414,7 @@ func (c *testPulumiClient) checkEnvironment(ctx context.Context, orgName, envNam showSecrets = opts[0].ShowSecrets } - checked, checkDiags := eval.CheckEnvironment(ctx, envName, environment, rot128{}, providers, envLoader, execContext, showSecrets) + checked, checkDiags := eval.CheckEnvironment(ctx, envName, environment, rot128{}, providers, rotators, envLoader, execContext, showSecrets) diags.Extend(checkDiags...) return checked, mapDiags(diags), nil } @@ -427,6 +434,7 @@ func (c *testPulumiClient) openEnvironment(ctx context.Context, orgName, name st } providers := &testProviders{} + rotators := &testRotators{} envLoader := &testEnvironments{orgName: orgName, environments: c.environments} execContext, err := esc.NewExecContext(make(map[string]esc.Value)) @@ -434,7 +442,7 @@ func (c *testPulumiClient) openEnvironment(ctx context.Context, orgName, name st return "", nil, fmt.Errorf("initializing the ESC exec context: %w", err) } - openEnv, evalDiags := eval.EvalEnvironment(ctx, name, decl, rot128{}, providers, envLoader, execContext) + openEnv, evalDiags := eval.EvalEnvironment(ctx, name, decl, rot128{}, providers, rotators, envLoader, execContext) diags.Extend(evalDiags...) if diags.HasErrors() { diff --git a/eval/eval.go b/eval/eval.go index 707f95ca..bb976e3e 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -86,10 +86,11 @@ func EvalEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, + rotators RotatorLoader, environments EnvironmentLoader, execContext *esc.ExecContext, ) (*esc.Environment, syntax.Diagnostics) { - opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, nil, environments, execContext, true, nil) + opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, rotators, environments, execContext, true, nil) return opened, diags } @@ -101,11 +102,12 @@ func CheckEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, + rotators RotatorLoader, environments EnvironmentLoader, execContext *esc.ExecContext, showSecrets bool, ) (*esc.Environment, syntax.Diagnostics) { - checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, nil, environments, execContext, showSecrets, nil) + checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, rotators, environments, execContext, showSecrets, nil) return checked, diags } diff --git a/eval/eval_test.go b/eval/eval_test.go index c895874c..6f12e6d9 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -339,11 +339,11 @@ func TestEval(t *testing.T) { sortEnvironmentDiagnostics(loadDiags) check, checkDiags := CheckEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testEnvironments{basePath}, execContext, showSecrets) + testRotators{}, &testEnvironments{basePath}, execContext, showSecrets) sortEnvironmentDiagnostics(checkDiags) actual, evalDiags := EvalEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testEnvironments{basePath}, execContext) + testRotators{}, &testEnvironments{basePath}, execContext) sortEnvironmentDiagnostics(evalDiags) var checkJSON any @@ -392,12 +392,12 @@ func TestEval(t *testing.T) { require.Equal(t, expected.LoadDiags, diags) check, diags := CheckEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testEnvironments{basePath}, execContext, showSecrets) + &testRotators{}, &testEnvironments{basePath}, execContext, showSecrets) sortEnvironmentDiagnostics(diags) require.Equal(t, expected.CheckDiags, diags) actual, diags := EvalEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testEnvironments{basePath}, execContext) + &testRotators{}, &testEnvironments{basePath}, execContext) sortEnvironmentDiagnostics(diags) require.Equal(t, expected.EvalDiags, diags) @@ -459,7 +459,7 @@ func benchmarkEval(b *testing.B, openDelay, loadDelay time.Duration) { require.Empty(b, loadDiags) _, evalDiags := EvalEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{benchDelay: openDelay}, - envs, execContext) + testRotators{}, envs, execContext) require.Empty(b, evalDiags) } } From 889c6f0a2a7282d1ac88c722bb8d12dca30bf288 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:38:35 -0800 Subject: [PATCH 08/30] license heading --- eval/patch.go | 14 ++++++++++++++ eval/rotate_example_test.go | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/eval/patch.go b/eval/patch.go index a299214e..b3bdc4fe 100644 --- a/eval/patch.go +++ b/eval/patch.go @@ -1,3 +1,17 @@ +// Copyright 2025, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package eval import ( diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index 5c04bf59..561a174d 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -1,3 +1,17 @@ +// Copyright 2025, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package eval import ( From e183ea7ac9d2fe74b9f0a8bbe667e0c031e0e78b Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:53:35 -0800 Subject: [PATCH 09/30] fix rotator.Open not getting called when not rotating --- eval/eval.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eval/eval.go b/eval/eval.go index bb976e3e..b80a79f4 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -1021,13 +1021,13 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { inputs, inputsOK := e.evaluateTypedExpr(repr.inputs, repr.inputSchema) state, stateOK := e.evaluateTypedExpr(repr.state, repr.stateSchema) - if !inputsOK || inputs.containsUnknowns() || !stateOK || state.containsUnknowns() || e.validating || e.rotatePaths == nil || err != nil { + if !inputsOK || inputs.containsUnknowns() || !stateOK || state.containsUnknowns() || e.validating || err != nil { v.unknown = true return v } // if rotating, invoke prior to open - if len(e.rotatePaths) == 0 || e.rotatePaths[x.path] { + if e.rotatePaths != nil && (len(e.rotatePaths) == 0 || e.rotatePaths[x.path]) { newState, err := rotator.Rotate( e.ctx, inputs.export("").Value.(map[string]esc.Value), From d17fcdaa0c679fecd94886ee295abff026e01afa Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:13:39 -0800 Subject: [PATCH 10/30] fix rotator loaders need to be present for opening imports --- eval/eval.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eval/eval.go b/eval/eval.go index b80a79f4..bc75f9df 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -512,8 +512,8 @@ func (e *evalContext) evaluateImport(myImports map[string]*value, decl *ast.Impo return } - // only rotate root environment - imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, nil, e.environments, e.imports, e.execContext, e.showSecrets, nil) + // we only want to rotate the root environment, so clear out rotatePaths when evaluating imports + imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, e.rotators, e.environments, e.imports, e.execContext, e.showSecrets, nil) v, diags := imp.evaluate() e.diags.Extend(diags...) From 4769a6935e270434f521791aa3b636e5cffe178e Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:42:34 -0800 Subject: [PATCH 11/30] initial snapshot test --- eval/eval_test.go | 55 +- eval/testdata/eval/rotate/env.yaml | 16 + eval/testdata/eval/rotate/expected.json | 3113 ++++++++++++++++++++++ eval/testdata/eval/rotate/overrides.json | 1 + 4 files changed, 3183 insertions(+), 2 deletions(-) create mode 100644 eval/testdata/eval/rotate/env.yaml create mode 100644 eval/testdata/eval/rotate/expected.json create mode 100644 eval/testdata/eval/rotate/overrides.json diff --git a/eval/eval_test.go b/eval/eval_test.go index 6f12e6d9..ebbbef4b 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "sort" "strings" "testing" @@ -280,8 +281,9 @@ func normalize[T any](t *testing.T, v T) T { func TestEval(t *testing.T) { type testOverrides struct { - ShowSecrets bool `json:"showSecrets,omitempty"` - RootEnvironment string `json:"rootEnvironment,omitempty"` + ShowSecrets bool `json:"showSecrets,omitempty"` + RootEnvironment string `json:"rootEnvironment,omitempty"` + RotatePaths []string `json:"rotatePaths,omitempty"` } type expectedData struct { @@ -293,6 +295,10 @@ func TestEval(t *testing.T) { Eval *esc.Environment `json:"eval,omitempty"` EvalJSONRedacted any `json:"evalJsonRedacted,omitempty"` EvalJSONRevealed any `json:"evalJSONRevealed,omitempty"` + RotateDiags syntax.Diagnostics `json:"rotateDiags,omitempty"` + Rotate *esc.Environment `json:"rotate,omitempty"` + RotateJSON any `json:"rotateJson,omitempty"` + RotatePatches []*Patch `json:"rotatePatches,omitempty"` } path := filepath.Join("testdata", "eval") @@ -332,6 +338,7 @@ func TestEval(t *testing.T) { environmentName = overrides.RootEnvironment } showSecrets := overrides.ShowSecrets + rotatePaths := overrides.RotatePaths if accept() { env, loadDiags, err := LoadYAMLBytes(environmentName, envBytes) @@ -346,9 +353,18 @@ func TestEval(t *testing.T) { testRotators{}, &testEnvironments{basePath}, execContext) sortEnvironmentDiagnostics(evalDiags) + var rotated *esc.Environment + var patches []*Patch + var rotateDiags syntax.Diagnostics + if rotatePaths != nil { + rotated, patches, rotateDiags = RotateEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, + testRotators{}, &testEnvironments{basePath}, execContext, rotatePaths) + } + var checkJSON any var evalJSONRedacted any var evalJSONRevealed any + var rotateJSON any if check != nil { check = normalize(t, check) checkJSON = esc.NewValue(check.Properties).ToJSON(true) @@ -358,6 +374,10 @@ func TestEval(t *testing.T) { evalJSONRedacted = esc.NewValue(actual.Properties).ToJSON(true) evalJSONRevealed = esc.NewValue(actual.Properties).ToJSON(false) } + if rotated != nil { + rotated = normalize(t, rotated) + rotateJSON = esc.NewValue(rotated.Properties).ToJSON(true) + } bytes, err := json.MarshalIndent(expectedData{ LoadDiags: loadDiags, @@ -368,6 +388,10 @@ func TestEval(t *testing.T) { EvalJSONRedacted: evalJSONRedacted, EvalJSONRevealed: evalJSONRevealed, CheckJSON: checkJSON, + RotateDiags: rotateDiags, + Rotate: rotated, + RotateJSON: rotateJSON, + RotatePatches: patches, }, "", " ") bytes = append(bytes, '\n') require.NoError(t, err) @@ -401,9 +425,26 @@ func TestEval(t *testing.T) { sortEnvironmentDiagnostics(diags) require.Equal(t, expected.EvalDiags, diags) + var rotated *esc.Environment + if rotatePaths != nil { + rotated_, patches, diags := RotateEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, + &testRotators{}, &testEnvironments{basePath}, execContext, rotatePaths) + + sortEnvironmentDiagnostics(diags) + require.Equal(t, expected.RotateDiags, diags) + + slices.SortFunc(patches, func(a, b *Patch) int { + return strings.Compare(a.DocPath, b.DocPath) + }) + require.Equal(t, expected.RotatePatches, patches) + + rotated = rotated_ + } + // work around a schema comparison issue due to the 'compiled' field by roundtripping through JSON check = normalize(t, check) actual = normalize(t, actual) + rotated = normalize(t, rotated) // work around a comparison issue when comparing nil slices/maps against zero-length slices/maps if actual != nil { @@ -426,8 +467,18 @@ func TestEval(t *testing.T) { t.Logf("check: %v", string(bytes)) } + if rotated != nil { + rotateJSON := esc.NewValue(check.Properties).ToJSON(true) + assert.Equal(t, expected.CheckJSON, rotateJSON) + + bytes, err := json.MarshalIndent(rotateJSON, "", " ") + require.NoError(t, err) + t.Logf("rotate: %v", string(bytes)) + } + assert.Equal(t, expected.Check, check) assert.Equal(t, expected.Eval, actual) + assert.Equal(t, expected.Rotate, rotated) }) } } diff --git a/eval/testdata/eval/rotate/env.yaml b/eval/testdata/eval/rotate/env.yaml new file mode 100644 index 00000000..780d38d9 --- /dev/null +++ b/eval/testdata/eval/rotate/env.yaml @@ -0,0 +1,16 @@ +values: + full: + fn::rotate: + provider: swap + inputs: {} + state: + a: bar1 + b: bar2 + short: + fn::rotate::swap: + state: + a: bar1 + b: bar2 + + reference: + test: ${full.a} ${short.b} \ No newline at end of file diff --git a/eval/testdata/eval/rotate/expected.json b/eval/testdata/eval/rotate/expected.json new file mode 100644 index 00000000..c93a663d --- /dev/null +++ b/eval/testdata/eval/rotate/expected.json @@ -0,0 +1,3113 @@ +{ + "check": { + "exprs": { + "full": { + "range": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "builtin": { + "name": "fn::rotate", + "nameRange": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 3, + "column": 15, + "byte": 30 + } + }, + "argSchema": { + "properties": { + "inputs": true, + "provider": { + "type": "string" + }, + "state": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "inputs", + "provider", + "state" + ] + }, + "arg": { + "range": { + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + }, + "object": { + "inputs": { + "range": { + "environment": "rotate", + "begin": { + "line": 5, + "column": 15, + "byte": 67 + }, + "end": { + "line": 5, + "column": 15, + "byte": 67 + } + }, + "schema": { + "type": "object" + } + }, + "provider": { + "range": { + "environment": "rotate", + "begin": { + "line": 4, + "column": 17, + "byte": 48 + }, + "end": { + "line": 4, + "column": 21, + "byte": 52 + } + }, + "schema": { + "type": "string", + "const": "swap" + }, + "literal": "swap" + }, + "state": { + "range": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 9, + "byte": 91 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "keyRanges": { + "a": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 9, + "byte": 91 + }, + "end": { + "line": 7, + "column": 10, + "byte": 92 + } + }, + "b": { + "environment": "rotate", + "begin": { + "line": 8, + "column": 9, + "byte": 107 + }, + "end": { + "line": 8, + "column": 10, + "byte": 108 + } + } + }, + "object": { + "a": { + "range": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 12, + "byte": 94 + }, + "end": { + "line": 7, + "column": 16, + "byte": 98 + } + }, + "schema": { + "type": "string", + "const": "bar1" + }, + "literal": "bar1" + }, + "b": { + "range": { + "environment": "rotate", + "begin": { + "line": 8, + "column": 12, + "byte": 110 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "type": "string", + "const": "bar2" + }, + "literal": "bar2" + } + } + } + } + } + } + }, + "reference": { + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + }, + "schema": { + "properties": { + "test": { + "type": "string" + } + }, + "type": "object", + "required": [ + "test" + ] + }, + "keyRanges": { + "test": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 9, + "byte": 0 + } + } + }, + "object": { + "test": { + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + }, + "schema": { + "type": "string" + }, + "interpolate": [ + { + "value": [ + { + "key": "full", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 13, + "byte": 2 + }, + "end": { + "line": 16, + "column": 17, + "byte": 6 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + }, + { + "key": "a", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 17, + "byte": 6 + }, + "end": { + "line": 16, + "column": 19, + "byte": 8 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + ] + }, + { + "text": " ", + "value": [ + { + "key": "short", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 23, + "byte": 12 + }, + "end": { + "line": 16, + "column": 28, + "byte": 17 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + }, + { + "key": "b", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 28, + "byte": 17 + }, + "end": { + "line": 16, + "column": 30, + "byte": 19 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + ] + } + ] + } + } + }, + "short": { + "range": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "builtin": { + "name": "fn::rotate::swap", + "nameRange": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 10, + "column": 21, + "byte": 144 + } + }, + "argSchema": { + "properties": { + "state": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "state" + ] + }, + "arg": { + "range": { + "environment": "rotate", + "begin": { + "line": 11, + "column": 7, + "byte": 152 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "type": "object" + }, + "object": { + "state": { + "range": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 9, + "byte": 167 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "keyRanges": { + "a": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 9, + "byte": 167 + }, + "end": { + "line": 12, + "column": 10, + "byte": 168 + } + }, + "b": { + "environment": "rotate", + "begin": { + "line": 13, + "column": 9, + "byte": 183 + }, + "end": { + "line": 13, + "column": 10, + "byte": 184 + } + } + }, + "object": { + "a": { + "range": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 12, + "byte": 170 + }, + "end": { + "line": 12, + "column": 16, + "byte": 174 + } + }, + "schema": { + "type": "string", + "const": "bar1" + }, + "literal": "bar1" + }, + "b": { + "range": { + "environment": "rotate", + "begin": { + "line": 13, + "column": 12, + "byte": 186 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "type": "string", + "const": "bar2" + }, + "literal": "bar2" + } + } + } + } + } + } + } + }, + "properties": { + "full": { + "unknown": true, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + }, + "reference": { + "value": { + "test": { + "value": "[unknown]", + "unknown": true, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + }, + "short": { + "unknown": true, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + } + }, + "schema": { + "properties": { + "full": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "reference": { + "properties": { + "test": { + "type": "string" + } + }, + "type": "object", + "required": [ + "test" + ] + }, + "short": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "full", + "reference", + "short" + ] + }, + "executionContext": { + "properties": { + "currentEnvironment": { + "value": { + "name": { + "value": "rotate", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + }, + "pulumi": { + "value": { + "user": { + "value": { + "id": { + "value": "USER_123", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + }, + "rootEnvironment": { + "value": { + "name": { + "value": "rotate", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "schema": { + "properties": { + "currentEnvironment": { + "properties": { + "name": { + "type": "string", + "const": "rotate" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "pulumi": { + "properties": { + "user": { + "properties": { + "id": { + "type": "string", + "const": "USER_123" + } + }, + "type": "object", + "required": [ + "id" + ] + } + }, + "type": "object", + "required": [ + "user" + ] + }, + "rootEnvironment": { + "properties": { + "name": { + "type": "string", + "const": "rotate" + } + }, + "type": "object", + "required": [ + "name" + ] + } + }, + "type": "object", + "required": [ + "currentEnvironment", + "pulumi", + "rootEnvironment" + ] + } + } + }, + "checkJson": { + "full": "[unknown]", + "reference": { + "test": "[unknown]" + }, + "short": "[unknown]" + }, + "eval": { + "exprs": { + "full": { + "range": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "builtin": { + "name": "fn::rotate", + "nameRange": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 3, + "column": 15, + "byte": 30 + } + }, + "argSchema": { + "properties": { + "inputs": true, + "provider": { + "type": "string" + }, + "state": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "inputs", + "provider", + "state" + ] + }, + "arg": { + "range": { + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + }, + "object": { + "inputs": { + "range": { + "environment": "rotate", + "begin": { + "line": 5, + "column": 15, + "byte": 67 + }, + "end": { + "line": 5, + "column": 15, + "byte": 67 + } + }, + "schema": { + "type": "object" + } + }, + "provider": { + "range": { + "environment": "rotate", + "begin": { + "line": 4, + "column": 17, + "byte": 48 + }, + "end": { + "line": 4, + "column": 21, + "byte": 52 + } + }, + "schema": { + "type": "string", + "const": "swap" + }, + "literal": "swap" + }, + "state": { + "range": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 9, + "byte": 91 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "keyRanges": { + "a": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 9, + "byte": 91 + }, + "end": { + "line": 7, + "column": 10, + "byte": 92 + } + }, + "b": { + "environment": "rotate", + "begin": { + "line": 8, + "column": 9, + "byte": 107 + }, + "end": { + "line": 8, + "column": 10, + "byte": 108 + } + } + }, + "object": { + "a": { + "range": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 12, + "byte": 94 + }, + "end": { + "line": 7, + "column": 16, + "byte": 98 + } + }, + "schema": { + "type": "string", + "const": "bar1" + }, + "literal": "bar1" + }, + "b": { + "range": { + "environment": "rotate", + "begin": { + "line": 8, + "column": 12, + "byte": 110 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "type": "string", + "const": "bar2" + }, + "literal": "bar2" + } + } + } + } + } + } + }, + "reference": { + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + }, + "schema": { + "properties": { + "test": { + "type": "string" + } + }, + "type": "object", + "required": [ + "test" + ] + }, + "keyRanges": { + "test": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 9, + "byte": 0 + } + } + }, + "object": { + "test": { + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + }, + "schema": { + "type": "string" + }, + "interpolate": [ + { + "value": [ + { + "key": "full", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 13, + "byte": 2 + }, + "end": { + "line": 16, + "column": 17, + "byte": 6 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + }, + { + "key": "a", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 17, + "byte": 6 + }, + "end": { + "line": 16, + "column": 19, + "byte": 8 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + ] + }, + { + "text": " ", + "value": [ + { + "key": "short", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 23, + "byte": 12 + }, + "end": { + "line": 16, + "column": 28, + "byte": 17 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + }, + { + "key": "b", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 28, + "byte": 17 + }, + "end": { + "line": 16, + "column": 30, + "byte": 19 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + ] + } + ] + } + } + }, + "short": { + "range": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "builtin": { + "name": "fn::rotate::swap", + "nameRange": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 10, + "column": 21, + "byte": 144 + } + }, + "argSchema": { + "properties": { + "state": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "state" + ] + }, + "arg": { + "range": { + "environment": "rotate", + "begin": { + "line": 11, + "column": 7, + "byte": 152 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "type": "object" + }, + "object": { + "state": { + "range": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 9, + "byte": 167 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "keyRanges": { + "a": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 9, + "byte": 167 + }, + "end": { + "line": 12, + "column": 10, + "byte": 168 + } + }, + "b": { + "environment": "rotate", + "begin": { + "line": 13, + "column": 9, + "byte": 183 + }, + "end": { + "line": 13, + "column": 10, + "byte": 184 + } + } + }, + "object": { + "a": { + "range": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 12, + "byte": 170 + }, + "end": { + "line": 12, + "column": 16, + "byte": 174 + } + }, + "schema": { + "type": "string", + "const": "bar1" + }, + "literal": "bar1" + }, + "b": { + "range": { + "environment": "rotate", + "begin": { + "line": 13, + "column": 12, + "byte": 186 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "type": "string", + "const": "bar2" + }, + "literal": "bar2" + } + } + } + } + } + } + } + }, + "properties": { + "full": { + "value": { + "a": { + "value": "bar1", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + }, + "b": { + "value": "bar2", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + }, + "reference": { + "value": { + "test": { + "value": "bar1 bar2", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + }, + "short": { + "value": { + "a": { + "value": "bar1", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + }, + "b": { + "value": "bar2", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + } + }, + "schema": { + "properties": { + "full": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "reference": { + "properties": { + "test": { + "type": "string" + } + }, + "type": "object", + "required": [ + "test" + ] + }, + "short": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "full", + "reference", + "short" + ] + }, + "executionContext": { + "properties": { + "currentEnvironment": { + "value": { + "name": { + "value": "rotate", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + }, + "pulumi": { + "value": { + "user": { + "value": { + "id": { + "value": "USER_123", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + }, + "rootEnvironment": { + "value": { + "name": { + "value": "rotate", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "schema": { + "properties": { + "currentEnvironment": { + "properties": { + "name": { + "type": "string", + "const": "rotate" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "pulumi": { + "properties": { + "user": { + "properties": { + "id": { + "type": "string", + "const": "USER_123" + } + }, + "type": "object", + "required": [ + "id" + ] + } + }, + "type": "object", + "required": [ + "user" + ] + }, + "rootEnvironment": { + "properties": { + "name": { + "type": "string", + "const": "rotate" + } + }, + "type": "object", + "required": [ + "name" + ] + } + }, + "type": "object", + "required": [ + "currentEnvironment", + "pulumi", + "rootEnvironment" + ] + } + } + }, + "evalJsonRedacted": { + "full": { + "a": "bar1", + "b": "bar2" + }, + "reference": { + "test": "bar1 bar2" + }, + "short": { + "a": "bar1", + "b": "bar2" + } + }, + "evalJSONRevealed": { + "full": { + "a": "bar1", + "b": "bar2" + }, + "reference": { + "test": "bar1 bar2" + }, + "short": { + "a": "bar1", + "b": "bar2" + } + }, + "rotate": { + "exprs": { + "full": { + "range": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar2" + }, + "b": { + "type": "string", + "const": "bar1" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "builtin": { + "name": "fn::rotate", + "nameRange": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 3, + "column": 15, + "byte": 30 + } + }, + "argSchema": { + "properties": { + "inputs": true, + "provider": { + "type": "string" + }, + "state": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "inputs", + "provider", + "state" + ] + }, + "arg": { + "range": { + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + }, + "object": { + "inputs": { + "range": { + "environment": "rotate", + "begin": { + "line": 5, + "column": 15, + "byte": 67 + }, + "end": { + "line": 5, + "column": 15, + "byte": 67 + } + }, + "schema": { + "type": "object" + } + }, + "provider": { + "range": { + "environment": "rotate", + "begin": { + "line": 4, + "column": 17, + "byte": 48 + }, + "end": { + "line": 4, + "column": 21, + "byte": 52 + } + }, + "schema": { + "type": "string", + "const": "swap" + }, + "literal": "swap" + }, + "state": { + "range": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 9, + "byte": 91 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "keyRanges": { + "a": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 9, + "byte": 91 + }, + "end": { + "line": 7, + "column": 10, + "byte": 92 + } + }, + "b": { + "environment": "rotate", + "begin": { + "line": 8, + "column": 9, + "byte": 107 + }, + "end": { + "line": 8, + "column": 10, + "byte": 108 + } + } + }, + "object": { + "a": { + "range": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 12, + "byte": 94 + }, + "end": { + "line": 7, + "column": 16, + "byte": 98 + } + }, + "schema": { + "type": "string", + "const": "bar1" + }, + "literal": "bar1" + }, + "b": { + "range": { + "environment": "rotate", + "begin": { + "line": 8, + "column": 12, + "byte": 110 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + }, + "schema": { + "type": "string", + "const": "bar2" + }, + "literal": "bar2" + } + } + } + } + } + } + }, + "reference": { + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + }, + "schema": { + "properties": { + "test": { + "type": "string" + } + }, + "type": "object", + "required": [ + "test" + ] + }, + "keyRanges": { + "test": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 9, + "byte": 0 + } + } + }, + "object": { + "test": { + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + }, + "schema": { + "type": "string" + }, + "interpolate": [ + { + "value": [ + { + "key": "full", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 13, + "byte": 2 + }, + "end": { + "line": 16, + "column": 17, + "byte": 6 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + }, + { + "key": "a", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 17, + "byte": 6 + }, + "end": { + "line": 16, + "column": 19, + "byte": 8 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + ] + }, + { + "text": " ", + "value": [ + { + "key": "short", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 23, + "byte": 12 + }, + "end": { + "line": 16, + "column": 28, + "byte": 17 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + }, + { + "key": "b", + "range": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 28, + "byte": 17 + }, + "end": { + "line": 16, + "column": 30, + "byte": 19 + } + }, + "value": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + ] + } + ] + } + } + }, + "short": { + "range": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar2" + }, + "b": { + "type": "string", + "const": "bar1" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "builtin": { + "name": "fn::rotate::swap", + "nameRange": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 10, + "column": 21, + "byte": 144 + } + }, + "argSchema": { + "properties": { + "state": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "state" + ] + }, + "arg": { + "range": { + "environment": "rotate", + "begin": { + "line": 11, + "column": 7, + "byte": 152 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "type": "object" + }, + "object": { + "state": { + "range": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 9, + "byte": 167 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "properties": { + "a": { + "type": "string", + "const": "bar1" + }, + "b": { + "type": "string", + "const": "bar2" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "keyRanges": { + "a": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 9, + "byte": 167 + }, + "end": { + "line": 12, + "column": 10, + "byte": 168 + } + }, + "b": { + "environment": "rotate", + "begin": { + "line": 13, + "column": 9, + "byte": 183 + }, + "end": { + "line": 13, + "column": 10, + "byte": 184 + } + } + }, + "object": { + "a": { + "range": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 12, + "byte": 170 + }, + "end": { + "line": 12, + "column": 16, + "byte": 174 + } + }, + "schema": { + "type": "string", + "const": "bar1" + }, + "literal": "bar1" + }, + "b": { + "range": { + "environment": "rotate", + "begin": { + "line": 13, + "column": 12, + "byte": 186 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + }, + "schema": { + "type": "string", + "const": "bar2" + }, + "literal": "bar2" + } + } + } + } + } + } + } + }, + "properties": { + "full": { + "value": { + "a": { + "value": "bar2", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + }, + "b": { + "value": "bar1", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 3, + "column": 5, + "byte": 20 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + }, + "reference": { + "value": { + "test": { + "value": "bar2 bar1", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 11, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 16, + "column": 5, + "byte": 0 + }, + "end": { + "line": 16, + "column": 31, + "byte": 0 + } + } + } + }, + "short": { + "value": { + "a": { + "value": "bar2", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + }, + "b": { + "value": "bar1", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 10, + "column": 5, + "byte": 128 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + } + }, + "schema": { + "properties": { + "full": { + "properties": { + "a": { + "type": "string", + "const": "bar2" + }, + "b": { + "type": "string", + "const": "bar1" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + }, + "reference": { + "properties": { + "test": { + "type": "string" + } + }, + "type": "object", + "required": [ + "test" + ] + }, + "short": { + "properties": { + "a": { + "type": "string", + "const": "bar2" + }, + "b": { + "type": "string", + "const": "bar1" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "type": "object", + "required": [ + "full", + "reference", + "short" + ] + }, + "executionContext": { + "properties": { + "currentEnvironment": { + "value": { + "name": { + "value": "rotate", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + }, + "pulumi": { + "value": { + "user": { + "value": { + "id": { + "value": "USER_123", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + }, + "rootEnvironment": { + "value": { + "name": { + "value": "rotate", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + "schema": { + "properties": { + "currentEnvironment": { + "properties": { + "name": { + "type": "string", + "const": "rotate" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "pulumi": { + "properties": { + "user": { + "properties": { + "id": { + "type": "string", + "const": "USER_123" + } + }, + "type": "object", + "required": [ + "id" + ] + } + }, + "type": "object", + "required": [ + "user" + ] + }, + "rootEnvironment": { + "properties": { + "name": { + "type": "string", + "const": "rotate" + } + }, + "type": "object", + "required": [ + "name" + ] + } + }, + "type": "object", + "required": [ + "currentEnvironment", + "pulumi", + "rootEnvironment" + ] + } + } + }, + "rotateJson": { + "full": { + "a": "bar2", + "b": "bar1" + }, + "reference": { + "test": "bar2 bar1" + }, + "short": { + "a": "bar2", + "b": "bar1" + } + }, + "rotatePatches": [ + { + "DocPath": "full.fn::rotate.state", + "Replacement": { + "value": { + "a": { + "value": "bar2", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 8, + "column": 12, + "byte": 110 + }, + "end": { + "line": 8, + "column": 16, + "byte": 114 + } + } + } + }, + "b": { + "value": "bar1", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 7, + "column": 12, + "byte": 94 + }, + "end": { + "line": 7, + "column": 16, + "byte": 98 + } + } + } + } + }, + "trace": { + "def": { + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + }, + { + "DocPath": "short.fn::rotate::swap.state", + "Replacement": { + "value": { + "a": { + "value": "bar2", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 13, + "column": 12, + "byte": 186 + }, + "end": { + "line": 13, + "column": 16, + "byte": 190 + } + } + } + }, + "b": { + "value": "bar1", + "trace": { + "def": { + "environment": "rotate", + "begin": { + "line": 12, + "column": 12, + "byte": 170 + }, + "end": { + "line": 12, + "column": 16, + "byte": 174 + } + } + } + } + }, + "trace": { + "def": { + "begin": { + "line": 0, + "column": 0, + "byte": 0 + }, + "end": { + "line": 0, + "column": 0, + "byte": 0 + } + } + } + } + } + ] +} diff --git a/eval/testdata/eval/rotate/overrides.json b/eval/testdata/eval/rotate/overrides.json new file mode 100644 index 00000000..41cd07c7 --- /dev/null +++ b/eval/testdata/eval/rotate/overrides.json @@ -0,0 +1 @@ +{"rotatePaths": []} From 14d609a2e418cc634fa50fb965449b96e10fa2a0 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:26:34 -0800 Subject: [PATCH 12/30] test valueToSecretJSON --- eval/patch_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 eval/patch_test.go diff --git a/eval/patch_test.go b/eval/patch_test.go new file mode 100644 index 00000000..aeff401d --- /dev/null +++ b/eval/patch_test.go @@ -0,0 +1,25 @@ +package eval + +import ( + "github.com/pulumi/esc" + "github.com/stretchr/testify/require" + "testing" +) + +func TestValueToSecretJSON(t *testing.T) { + t.Run("nested secrets", func(t *testing.T) { + actual := valueToSecretJSON(esc.NewValue(map[string]esc.Value{ + "foo": esc.NewValue(map[string]esc.Value{ + "bar": esc.NewSecret("secret"), + }), + })) + expected := map[string]any{ + "foo": map[string]any{ + "bar": map[string]any{ + "fn::secret": "secret", + }, + }, + } + require.Equal(t, expected, actual) + }) +} From 6042f218696f265cf00a6501af4fc167e9c08267 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:32:25 -0800 Subject: [PATCH 13/30] lint --- eval/patch_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/eval/patch_test.go b/eval/patch_test.go index aeff401d..f94567c9 100644 --- a/eval/patch_test.go +++ b/eval/patch_test.go @@ -1,3 +1,17 @@ +// Copyright 2025, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package eval import ( From 5910dc76c49fc5c9deb084c27392bbddbd9ac4aa Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:23:19 -0800 Subject: [PATCH 14/30] changelog --- CHANGELOG_PENDING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ed1d4f89..eacdb302 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -2,8 +2,10 @@ - Fix diagnostic messages when updating environment with invalid definition [#422](https://github.com/pulumi/esc/pull/422) +- Introduce support for rotating static credentials via `fn::rotate` providers [432](https://github.com/pulumi/esc/pull/432) ### Bug Fixes ### Breaking changes +- eval.CheckEnvironment and eval.EvalEnvironment now require an additional RotatorLoader param \ No newline at end of file From 1922995d2fbeac8f7f81ab0dea9da0b7620f3612 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:31:33 -0800 Subject: [PATCH 15/30] re-align rotator and provider interfaces? --- CHANGELOG_PENDING.md | 2 -- analysis/common_test.go | 6 ----- analysis/describe_test.go | 4 ++-- analysis/traversal_test.go | 2 +- cmd/esc/cli/cli_test.go | 12 ++-------- eval/eval.go | 47 ++++++++++++++++--------------------- eval/eval_test.go | 34 +++++++++------------------ eval/rotate_example_test.go | 2 +- provider.go | 7 +++--- 9 files changed, 40 insertions(+), 76 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index eacdb302..e9e5b0ad 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -7,5 +7,3 @@ ### Bug Fixes ### Breaking changes - -- eval.CheckEnvironment and eval.EvalEnvironment now require an additional RotatorLoader param \ No newline at end of file diff --git a/analysis/common_test.go b/analysis/common_test.go index e987a689..c21d8c13 100644 --- a/analysis/common_test.go +++ b/analysis/common_test.go @@ -71,12 +71,6 @@ func (testProviders) LoadProvider(ctx context.Context, name string) (esc.Provide return nil, fmt.Errorf("unknown provider %q", name) } -type testRotators struct{} - -func (testRotators) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { - return nil, fmt.Errorf("unknown provider %q", name) -} - type testEnvironments struct{} func (testEnvironments) LoadEnvironment(ctx context.Context, name string) ([]byte, eval.Decrypter, error) { diff --git a/analysis/describe_test.go b/analysis/describe_test.go index c61ecefb..35789693 100644 --- a/analysis/describe_test.go +++ b/analysis/describe_test.go @@ -34,7 +34,7 @@ func TestDescribe(t *testing.T) { execContext, err := esc.NewExecContext(make(map[string]esc.Value)) require.NoError(t, err) - env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testRotators{}, testEnvironments{}, execContext, false) + env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testEnvironments{}, execContext, false) require.Empty(t, diags) analysis := New(*env, map[string]*schema.Schema{"test": testProviderSchema}) @@ -108,7 +108,7 @@ func TestDescribeOpen(t *testing.T) { execContext, err := esc.NewExecContext(make(map[string]esc.Value)) require.NoError(t, err) - env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testRotators{}, testEnvironments{}, execContext, false) + env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testEnvironments{}, execContext, false) require.Empty(t, diags) analysis := New(*env, map[string]*schema.Schema{"test": testProviderSchema}) diff --git a/analysis/traversal_test.go b/analysis/traversal_test.go index 41a27dfe..60cc79c1 100644 --- a/analysis/traversal_test.go +++ b/analysis/traversal_test.go @@ -33,7 +33,7 @@ func TestExpressionAt(t *testing.T) { execContext, err := esc.NewExecContext(make(map[string]esc.Value)) require.NoError(t, err) - env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testRotators{}, testEnvironments{}, execContext, false) + env, diags := eval.CheckEnvironment(context.Background(), "def", syntax, nil, testProviders{}, testEnvironments{}, execContext, false) require.Empty(t, diags) analysis := New(*env, map[string]*schema.Schema{"test": testProviderSchema}) diff --git a/cmd/esc/cli/cli_test.go b/cmd/esc/cli/cli_test.go index 2014e69b..87674f71 100644 --- a/cmd/esc/cli/cli_test.go +++ b/cmd/esc/cli/cli_test.go @@ -215,12 +215,6 @@ func (testProviders) LoadProvider(ctx context.Context, name string) (esc.Provide return nil, fmt.Errorf("unknown provider %q", name) } -type testRotators struct{} - -func (testRotators) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { - return nil, fmt.Errorf("unknown rotator %q", name) -} - type rot128 struct{} func (rot128) Encrypt(_ context.Context, plaintext []byte) ([]byte, error) { @@ -401,7 +395,6 @@ func (c *testPulumiClient) checkEnvironment(ctx context.Context, orgName, envNam } providers := &testProviders{} - rotators := &testRotators{} envLoader := &testEnvironments{orgName: orgName, environments: c.environments} execContext, err := esc.NewExecContext(make(map[string]esc.Value)) @@ -414,7 +407,7 @@ func (c *testPulumiClient) checkEnvironment(ctx context.Context, orgName, envNam showSecrets = opts[0].ShowSecrets } - checked, checkDiags := eval.CheckEnvironment(ctx, envName, environment, rot128{}, providers, rotators, envLoader, execContext, showSecrets) + checked, checkDiags := eval.CheckEnvironment(ctx, envName, environment, rot128{}, providers, envLoader, execContext, showSecrets) diags.Extend(checkDiags...) return checked, mapDiags(diags), nil } @@ -434,7 +427,6 @@ func (c *testPulumiClient) openEnvironment(ctx context.Context, orgName, name st } providers := &testProviders{} - rotators := &testRotators{} envLoader := &testEnvironments{orgName: orgName, environments: c.environments} execContext, err := esc.NewExecContext(make(map[string]esc.Value)) @@ -442,7 +434,7 @@ func (c *testPulumiClient) openEnvironment(ctx context.Context, orgName, name st return "", nil, fmt.Errorf("initializing the ESC exec context: %w", err) } - openEnv, evalDiags := eval.EvalEnvironment(ctx, name, decl, rot128{}, providers, rotators, envLoader, execContext) + openEnv, evalDiags := eval.EvalEnvironment(ctx, name, decl, rot128{}, providers, envLoader, execContext) diags.Extend(evalDiags...) if diags.HasErrors() { diff --git a/eval/eval.go b/eval/eval.go index bc75f9df..73aa9320 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -39,12 +39,6 @@ type ProviderLoader interface { LoadProvider(ctx context.Context, name string) (esc.Provider, error) } -// A RotatorLoader provides the environment evaluator the capability to load rotators. -type RotatorLoader interface { - // LoadRotator loads the rotator with the given name. - LoadRotator(ctx context.Context, name string) (esc.Rotator, error) -} - // An EnvironmentLoader provides the environment evaluator the capability to load imported environment definitions. type EnvironmentLoader interface { // LoadEnvironment loads the definition for the environment with the given name. @@ -86,11 +80,10 @@ func EvalEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, - rotators RotatorLoader, environments EnvironmentLoader, execContext *esc.ExecContext, ) (*esc.Environment, syntax.Diagnostics) { - opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, rotators, environments, execContext, true, nil) + opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, nil) return opened, diags } @@ -102,12 +95,11 @@ func CheckEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, - rotators RotatorLoader, environments EnvironmentLoader, execContext *esc.ExecContext, showSecrets bool, ) (*esc.Environment, syntax.Diagnostics) { - checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, rotators, environments, execContext, showSecrets, nil) + checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, environments, execContext, showSecrets, nil) return checked, diags } @@ -119,7 +111,6 @@ func RotateEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, - rotators RotatorLoader, environments EnvironmentLoader, execContext *esc.ExecContext, paths []string, @@ -128,7 +119,7 @@ func RotateEnvironment( for _, path := range paths { rotatePaths[path] = true } - return evalEnvironment(ctx, false, name, env, decrypter, providers, rotators, environments, execContext, true, rotatePaths) + return evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, rotatePaths) } // evalEnvironment evaluates an environment and exports the result of evaluation. @@ -139,7 +130,6 @@ func evalEnvironment( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, - rotators RotatorLoader, envs EnvironmentLoader, execContext *esc.ExecContext, showSecrets bool, @@ -149,7 +139,7 @@ func evalEnvironment( return nil, nil, nil } - ec := newEvalContext(ctx, validating, name, env, decrypter, providers, rotators, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) + ec := newEvalContext(ctx, validating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) v, diags := ec.evaluate() s := schema.Never().Schema() @@ -188,7 +178,6 @@ type evalContext struct { env *ast.EnvironmentDecl // the root of the environment AST decrypter Decrypter // the decrypter to use for the environment providers ProviderLoader // the provider loader to use - rotators RotatorLoader // the rotator loader to use environments EnvironmentLoader // the environment loader to use imports map[string]*imported // the shared set of imported environments execContext *esc.ExecContext // evaluation context used for interpolation @@ -211,7 +200,6 @@ func newEvalContext( env *ast.EnvironmentDecl, decrypter Decrypter, providers ProviderLoader, - rotators RotatorLoader, environments EnvironmentLoader, imports map[string]*imported, execContext *esc.ExecContext, @@ -226,7 +214,6 @@ func newEvalContext( env: env, decrypter: decrypter, providers: providers, - rotators: rotators, environments: environments, imports: imports, execContext: execContext.CopyForEnv(name), @@ -513,7 +500,7 @@ func (e *evalContext) evaluateImport(myImports map[string]*value, decl *ast.Impo } // we only want to rotate the root environment, so clear out rotatePaths when evaluating imports - imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, e.rotators, e.environments, e.imports, e.execContext, e.showSecrets, nil) + imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets, nil) v, diags := imp.evaluate() e.diags.Extend(diags...) @@ -985,6 +972,18 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { return unexport(output, x) } +func loadRotator(ctx context.Context, providers ProviderLoader, name string) (esc.Rotator, error) { + provider, err := providers.LoadProvider(ctx, name) + if err != nil { + return nil, err + } + rotator, ok := provider.(esc.Rotator) + if !ok { + return nil, fmt.Errorf("provider is not a rotator") + } + return rotator, nil +} + // evaluateBuiltinOpen evaluates a call to the fn::rotate builtin. func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { v := &value{def: x} @@ -996,21 +995,16 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { return v } - rotator, err := e.rotators.LoadRotator(e.ctx, repr.node.Provider.GetValue()) + rotator, err := loadRotator(e.ctx, e.providers, repr.node.Provider.GetValue()) if err != nil { e.errorf(repr.syntax(), "%v", err) } else { - inputSchema, stateSchema, outputSchema := rotator.Schema() + inputSchema, outputSchema := rotator.Schema() if err := inputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) } else { repr.inputSchema = inputSchema } - if err := stateSchema.Compile(); err != nil { - e.errorf(repr.syntax(), "internal error: invalid state schema (%v)", err) - } else { - repr.stateSchema = stateSchema - } if err := outputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid schema (%v)", err) } else { @@ -1039,7 +1033,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { return v } - // todo: validate newState conforms to state schema + // todo: validate newState conforms to output schema e.patchOutputs = append(e.patchOutputs, &Patch{ // rotation output is written back to the fn's `state` input @@ -1053,7 +1047,6 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { output, err := rotator.Open( e.ctx, - inputs.export("").Value.(map[string]esc.Value), state.export("").Value.(map[string]esc.Value), e.execContext, ) diff --git a/eval/eval_test.go b/eval/eval_test.go index ebbbef4b..236db46c 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -139,20 +139,16 @@ func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, conte type swapRotator struct{} -func (swapRotator) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { +func (swapRotator) Schema() (*schema.Schema, *schema.Schema) { inputSchema := schema.Always() - stateSchema := schema.Record(schema.BuilderMap{ - "a": schema.String(), - "b": schema.String(), - }).Schema() outputSchema := schema.Record(schema.BuilderMap{ "a": schema.String(), "b": schema.String(), }).Schema() - return inputSchema, stateSchema, outputSchema + return inputSchema, outputSchema } -func (swapRotator) Open(ctx context.Context, inputs, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (swapRotator) Open(ctx context.Context, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(state), nil } @@ -178,18 +174,10 @@ func (tp testProviders) LoadProvider(ctx context.Context, name string) (esc.Prov return testProvider{}, nil case "bench": return benchProvider{delay: tp.benchDelay}, nil - } - return nil, fmt.Errorf("unknown provider %q", name) -} - -type testRotators struct{} - -func (testRotators) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { - switch name { case "swap": return swapRotator{}, nil } - return nil, fmt.Errorf("unknown rotator %q", name) + return nil, fmt.Errorf("unknown provider %q", name) } type testEnvironments struct { @@ -346,11 +334,11 @@ func TestEval(t *testing.T) { sortEnvironmentDiagnostics(loadDiags) check, checkDiags := CheckEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - testRotators{}, &testEnvironments{basePath}, execContext, showSecrets) + &testEnvironments{basePath}, execContext, showSecrets) sortEnvironmentDiagnostics(checkDiags) actual, evalDiags := EvalEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - testRotators{}, &testEnvironments{basePath}, execContext) + &testEnvironments{basePath}, execContext) sortEnvironmentDiagnostics(evalDiags) var rotated *esc.Environment @@ -358,7 +346,7 @@ func TestEval(t *testing.T) { var rotateDiags syntax.Diagnostics if rotatePaths != nil { rotated, patches, rotateDiags = RotateEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - testRotators{}, &testEnvironments{basePath}, execContext, rotatePaths) + &testEnvironments{basePath}, execContext, rotatePaths) } var checkJSON any @@ -416,19 +404,19 @@ func TestEval(t *testing.T) { require.Equal(t, expected.LoadDiags, diags) check, diags := CheckEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testRotators{}, &testEnvironments{basePath}, execContext, showSecrets) + &testEnvironments{basePath}, execContext, showSecrets) sortEnvironmentDiagnostics(diags) require.Equal(t, expected.CheckDiags, diags) actual, diags := EvalEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testRotators{}, &testEnvironments{basePath}, execContext) + &testEnvironments{basePath}, execContext) sortEnvironmentDiagnostics(diags) require.Equal(t, expected.EvalDiags, diags) var rotated *esc.Environment if rotatePaths != nil { rotated_, patches, diags := RotateEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testRotators{}, &testEnvironments{basePath}, execContext, rotatePaths) + &testEnvironments{basePath}, execContext, rotatePaths) sortEnvironmentDiagnostics(diags) require.Equal(t, expected.RotateDiags, diags) @@ -510,7 +498,7 @@ func benchmarkEval(b *testing.B, openDelay, loadDelay time.Duration) { require.Empty(b, loadDiags) _, evalDiags := EvalEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{benchDelay: openDelay}, - testRotators{}, envs, execContext) + envs, execContext) require.Empty(b, evalDiags) } } diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index 561a174d..3ce841a5 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -49,7 +49,7 @@ values: // rotate the environment execContext, err := esc.NewExecContext(nil) require.NoError(t, err) - _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, testProviders{}, testRotators{}, &testEnvironments{}, execContext, nil) + _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, testProviders{}, &testEnvironments{}, execContext, nil) require.Len(t, diags, 0) // writeback state patches diff --git a/provider.go b/provider.go index 8265c24c..538ef677 100644 --- a/provider.go +++ b/provider.go @@ -33,11 +33,10 @@ type Provider interface { // A Rotator enables environments to rotate a secret. // Rotators persist internal state by writing back to the environment. type Rotator interface { - // Schema returns the provider's input, state, and output schemata. - Schema() (inputs, state, outputs *schema.Schema) + Provider - // Open retrieves the provider's secrets. - Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error) + // Open retrieves the provider's secrets, using persisted state. + Open(ctx context.Context, state map[string]Value, executionContext EnvExecContext) (Value, error) // Rotate rotates the provider's secret, and returns the rotator's new state to be persisted. Rotate(ctx context.Context, inputs, state map[string]Value) (Value, error) From 9648bb6493f032f88aa014226b189c8f3d3fc63d Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:50:21 -0800 Subject: [PATCH 16/30] add rotating flag instead of overloading rotatePaths --- eval/eval.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/eval/eval.go b/eval/eval.go index 73aa9320..5b976fda 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -83,7 +83,7 @@ func EvalEnvironment( environments EnvironmentLoader, execContext *esc.ExecContext, ) (*esc.Environment, syntax.Diagnostics) { - opened, _, diags := evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, nil) + opened, _, diags := evalEnvironment(ctx, false, false, name, env, decrypter, providers, environments, execContext, true, nil) return opened, diags } @@ -99,7 +99,7 @@ func CheckEnvironment( execContext *esc.ExecContext, showSecrets bool, ) (*esc.Environment, syntax.Diagnostics) { - checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, environments, execContext, showSecrets, nil) + checked, _, diags := evalEnvironment(ctx, true, false, name, env, decrypter, providers, environments, execContext, showSecrets, nil) return checked, diags } @@ -119,13 +119,14 @@ func RotateEnvironment( for _, path := range paths { rotatePaths[path] = true } - return evalEnvironment(ctx, false, name, env, decrypter, providers, environments, execContext, true, rotatePaths) + return evalEnvironment(ctx, false, true, name, env, decrypter, providers, environments, execContext, true, rotatePaths) } // evalEnvironment evaluates an environment and exports the result of evaluation. func evalEnvironment( ctx context.Context, validating bool, + rotating bool, name string, env *ast.EnvironmentDecl, decrypter Decrypter, @@ -139,7 +140,7 @@ func evalEnvironment( return nil, nil, nil } - ec := newEvalContext(ctx, validating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) + ec := newEvalContext(ctx, validating, rotating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) v, diags := ec.evaluate() s := schema.Never().Schema() @@ -173,6 +174,7 @@ type imported struct { type evalContext struct { ctx context.Context // the cancellation context for evaluation validating bool // true if we are only checking the environment + rotating bool // true if we are invoking rotators showSecrets bool // true if secrets should be decrypted during validation name string // the name of the environment env *ast.EnvironmentDecl // the root of the environment AST @@ -187,7 +189,7 @@ type evalContext struct { root *expr // the root expression base *value // the base value - rotatePaths map[string]bool // when non-nil, specifies providers to rotate. if empty, the full environment is rotated. + rotatePaths map[string]bool // specifies paths to rotate. if empty, the full environment is rotated. patchOutputs []*Patch // updated rotation state to be written back to the environment definition diags syntax.Diagnostics // diagnostics generated during evaluation @@ -196,6 +198,7 @@ type evalContext struct { func newEvalContext( ctx context.Context, validating bool, + rotating bool, name string, env *ast.EnvironmentDecl, decrypter Decrypter, @@ -209,6 +212,7 @@ func newEvalContext( return &evalContext{ ctx: ctx, validating: validating, + rotating: rotating, showSecrets: showSecrets, name: name, env: env, @@ -499,8 +503,8 @@ func (e *evalContext) evaluateImport(myImports map[string]*value, decl *ast.Impo return } - // we only want to rotate the root environment, so clear out rotatePaths when evaluating imports - imp := newEvalContext(e.ctx, e.validating, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets, nil) + // we only want to rotate the root environment, so set rotating flag to false when evaluating imports + imp := newEvalContext(e.ctx, e.validating, false, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets, nil) v, diags := imp.evaluate() e.diags.Extend(diags...) @@ -1021,7 +1025,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { } // if rotating, invoke prior to open - if e.rotatePaths != nil && (len(e.rotatePaths) == 0 || e.rotatePaths[x.path]) { + if e.rotating && (len(e.rotatePaths) == 0 || e.rotatePaths[x.path]) { newState, err := rotator.Rotate( e.ctx, inputs.export("").Value.(map[string]esc.Value), From e3f06d741ddade9beee360215e2e3ef2b08a9d55 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:06:22 -0800 Subject: [PATCH 17/30] fix evaluating state input against correct schema --- eval/eval.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eval/eval.go b/eval/eval.go index 5b976fda..351fdc1c 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -1013,6 +1013,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { e.errorf(repr.syntax(), "internal error: invalid schema (%v)", err) } else { x.schema = outputSchema + repr.stateSchema = outputSchema // state input schema == output schema } } v.schema = x.schema From a208a03e6a11b74558214072ce8da3a29f841017 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:05:23 -0800 Subject: [PATCH 18/30] drop support for rotating individual paths for now --- eval/eval.go | 23 +++++++---------------- eval/eval_test.go | 16 ++++++++-------- eval/rotate_example_test.go | 2 +- eval/testdata/eval/rotate/overrides.json | 2 +- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/eval/eval.go b/eval/eval.go index 351fdc1c..a940efe2 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -83,7 +83,7 @@ func EvalEnvironment( environments EnvironmentLoader, execContext *esc.ExecContext, ) (*esc.Environment, syntax.Diagnostics) { - opened, _, diags := evalEnvironment(ctx, false, false, name, env, decrypter, providers, environments, execContext, true, nil) + opened, _, diags := evalEnvironment(ctx, false, false, name, env, decrypter, providers, environments, execContext, true) return opened, diags } @@ -99,7 +99,7 @@ func CheckEnvironment( execContext *esc.ExecContext, showSecrets bool, ) (*esc.Environment, syntax.Diagnostics) { - checked, _, diags := evalEnvironment(ctx, true, false, name, env, decrypter, providers, environments, execContext, showSecrets, nil) + checked, _, diags := evalEnvironment(ctx, true, false, name, env, decrypter, providers, environments, execContext, showSecrets) return checked, diags } @@ -113,13 +113,8 @@ func RotateEnvironment( providers ProviderLoader, environments EnvironmentLoader, execContext *esc.ExecContext, - paths []string, ) (*esc.Environment, []*Patch, syntax.Diagnostics) { - rotatePaths := map[string]bool{} - for _, path := range paths { - rotatePaths[path] = true - } - return evalEnvironment(ctx, false, true, name, env, decrypter, providers, environments, execContext, true, rotatePaths) + return evalEnvironment(ctx, false, true, name, env, decrypter, providers, environments, execContext, true) } // evalEnvironment evaluates an environment and exports the result of evaluation. @@ -134,13 +129,12 @@ func evalEnvironment( envs EnvironmentLoader, execContext *esc.ExecContext, showSecrets bool, - rotatePaths map[string]bool, ) (*esc.Environment, []*Patch, syntax.Diagnostics) { if env == nil || (len(env.Values.GetEntries()) == 0 && len(env.Imports.GetElements()) == 0) { return nil, nil, nil } - ec := newEvalContext(ctx, validating, rotating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets, rotatePaths) + ec := newEvalContext(ctx, validating, rotating, name, env, decrypter, providers, envs, map[string]*imported{}, execContext, showSecrets) v, diags := ec.evaluate() s := schema.Never().Schema() @@ -189,8 +183,7 @@ type evalContext struct { root *expr // the root expression base *value // the base value - rotatePaths map[string]bool // specifies paths to rotate. if empty, the full environment is rotated. - patchOutputs []*Patch // updated rotation state to be written back to the environment definition + patchOutputs []*Patch // updated rotation state generated during evaluation, to be written back to the environment definition diags syntax.Diagnostics // diagnostics generated during evaluation } @@ -207,7 +200,6 @@ func newEvalContext( imports map[string]*imported, execContext *esc.ExecContext, showSecrets bool, - rotatePaths map[string]bool, ) *evalContext { return &evalContext{ ctx: ctx, @@ -221,7 +213,6 @@ func newEvalContext( environments: environments, imports: imports, execContext: execContext.CopyForEnv(name), - rotatePaths: rotatePaths, } } @@ -504,7 +495,7 @@ func (e *evalContext) evaluateImport(myImports map[string]*value, decl *ast.Impo } // we only want to rotate the root environment, so set rotating flag to false when evaluating imports - imp := newEvalContext(e.ctx, e.validating, false, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets, nil) + imp := newEvalContext(e.ctx, e.validating, false, name, env, dec, e.providers, e.environments, e.imports, e.execContext, e.showSecrets) v, diags := imp.evaluate() e.diags.Extend(diags...) @@ -1026,7 +1017,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { } // if rotating, invoke prior to open - if e.rotating && (len(e.rotatePaths) == 0 || e.rotatePaths[x.path]) { + if e.rotating { newState, err := rotator.Rotate( e.ctx, inputs.export("").Value.(map[string]esc.Value), diff --git a/eval/eval_test.go b/eval/eval_test.go index 236db46c..bebd46a3 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -269,9 +269,9 @@ func normalize[T any](t *testing.T, v T) T { func TestEval(t *testing.T) { type testOverrides struct { - ShowSecrets bool `json:"showSecrets,omitempty"` - RootEnvironment string `json:"rootEnvironment,omitempty"` - RotatePaths []string `json:"rotatePaths,omitempty"` + ShowSecrets bool `json:"showSecrets,omitempty"` + RootEnvironment string `json:"rootEnvironment,omitempty"` + Rotate bool `json:"rotate,omitempty"` } type expectedData struct { @@ -326,7 +326,7 @@ func TestEval(t *testing.T) { environmentName = overrides.RootEnvironment } showSecrets := overrides.ShowSecrets - rotatePaths := overrides.RotatePaths + doRotate := overrides.Rotate if accept() { env, loadDiags, err := LoadYAMLBytes(environmentName, envBytes) @@ -344,9 +344,9 @@ func TestEval(t *testing.T) { var rotated *esc.Environment var patches []*Patch var rotateDiags syntax.Diagnostics - if rotatePaths != nil { + if doRotate { rotated, patches, rotateDiags = RotateEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testEnvironments{basePath}, execContext, rotatePaths) + &testEnvironments{basePath}, execContext) } var checkJSON any @@ -414,9 +414,9 @@ func TestEval(t *testing.T) { require.Equal(t, expected.EvalDiags, diags) var rotated *esc.Environment - if rotatePaths != nil { + if doRotate { rotated_, patches, diags := RotateEnvironment(context.Background(), environmentName, env, rot128{}, testProviders{}, - &testEnvironments{basePath}, execContext, rotatePaths) + &testEnvironments{basePath}, execContext) sortEnvironmentDiagnostics(diags) require.Equal(t, expected.RotateDiags, diags) diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index 3ce841a5..c464abf8 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -49,7 +49,7 @@ values: // rotate the environment execContext, err := esc.NewExecContext(nil) require.NoError(t, err) - _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, testProviders{}, &testEnvironments{}, execContext, nil) + _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, testProviders{}, &testEnvironments{}, execContext) require.Len(t, diags, 0) // writeback state patches diff --git a/eval/testdata/eval/rotate/overrides.json b/eval/testdata/eval/rotate/overrides.json index 41cd07c7..23e284f0 100644 --- a/eval/testdata/eval/rotate/overrides.json +++ b/eval/testdata/eval/rotate/overrides.json @@ -1 +1 @@ -{"rotatePaths": []} +{"rotate": true} From 740e99945b188fda38825e2657c9f664fc02fdbe Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Tue, 21 Jan 2025 22:26:50 -0800 Subject: [PATCH 19/30] merge provider and rotator interfaces, pass state param to open --- analysis/common_test.go | 6 +++--- cmd/esc/cli/cli_test.go | 6 +++--- eval/eval.go | 15 ++++++++++----- eval/eval_test.go | 34 +++++++++++++++++++--------------- provider.go | 7 ++----- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/analysis/common_test.go b/analysis/common_test.go index c21d8c13..d5305e10 100644 --- a/analysis/common_test.go +++ b/analysis/common_test.go @@ -53,11 +53,11 @@ var testProviderSchema = schema.Object(). type testProvider struct{} -func (testProvider) Schema() (*schema.Schema, *schema.Schema) { - return testProviderSchema, schema.Always() +func (testProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { + return testProviderSchema, nil, schema.Always() } -func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (testProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } diff --git a/cmd/esc/cli/cli_test.go b/cmd/esc/cli/cli_test.go index 87674f71..a6f45edd 100644 --- a/cmd/esc/cli/cli_test.go +++ b/cmd/esc/cli/cli_test.go @@ -198,11 +198,11 @@ func (w *testPulumiWorkspace) GetAccount(key string) (workspace.Account, error) type testProvider struct{} -func (testProvider) Schema() (*schema.Schema, *schema.Schema) { - return schema.Always(), schema.Always() +func (testProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { + return schema.Always(), nil, schema.Always() } -func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (testProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } diff --git a/eval/eval.go b/eval/eval.go index a940efe2..006ce54a 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -937,7 +937,7 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { if err != nil { e.errorf(repr.syntax(), "%v", err) } else { - inputSchema, outputSchema := provider.Schema() + inputSchema, _, outputSchema := provider.Schema() if err := inputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) } else { @@ -958,7 +958,7 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { return v } - output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), e.execContext) + output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), nil, e.execContext) if err != nil { e.errorf(repr.syntax(), "%s", err.Error()) v.unknown = true @@ -994,17 +994,21 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { if err != nil { e.errorf(repr.syntax(), "%v", err) } else { - inputSchema, outputSchema := rotator.Schema() + inputSchema, stateSchema, outputSchema := rotator.Schema() if err := inputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) } else { repr.inputSchema = inputSchema } + if err := stateSchema.Compile(); err != nil { + e.errorf(repr.syntax(), "internal error: invalid state schema (%v)", err) + } else { + repr.stateSchema = stateSchema + } if err := outputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid schema (%v)", err) } else { x.schema = outputSchema - repr.stateSchema = outputSchema // state input schema == output schema } } v.schema = x.schema @@ -1029,7 +1033,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { return v } - // todo: validate newState conforms to output schema + // todo: validate newState conforms to state schema e.patchOutputs = append(e.patchOutputs, &Patch{ // rotation output is written back to the fn's `state` input @@ -1043,6 +1047,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { output, err := rotator.Open( e.ctx, + inputs.export("").Value.(map[string]esc.Value), state.export("").Value.(map[string]esc.Value), e.execContext, ) diff --git a/eval/eval_test.go b/eval/eval_test.go index bebd46a3..1a2cddbb 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -43,17 +43,17 @@ func accept() bool { type errorProvider struct{} -func (errorProvider) Schema() (*schema.Schema, *schema.Schema) { - return schema.Record(schema.BuilderMap{"why": schema.String()}).Schema(), schema.Always() +func (errorProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { + return schema.Record(schema.BuilderMap{"why": schema.String()}).Schema(), nil, schema.Always() } -func (errorProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (errorProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { return esc.Value{}, errors.New(inputs["why"].Value.(string)) } type testSchemaProvider struct{} -func (testSchemaProvider) Schema() (*schema.Schema, *schema.Schema) { +func (testSchemaProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { s := schema.Object(). Defs(schema.BuilderMap{ "defRecord": schema.Record(schema.BuilderMap{ @@ -107,10 +107,10 @@ func (testSchemaProvider) Schema() (*schema.Schema, *schema.Schema) { }). Schema() - return s, s + return s, nil, s } -func (testSchemaProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (testSchemaProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } @@ -118,37 +118,41 @@ type benchProvider struct { delay time.Duration } -func (benchProvider) Schema() (*schema.Schema, *schema.Schema) { - return schema.Always(), schema.Always() +func (benchProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { + return schema.Always(), nil, schema.Always() } -func (p benchProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (p benchProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { time.Sleep(p.delay) return esc.NewValue(p.delay.String()), nil } type testProvider struct{} -func (testProvider) Schema() (*schema.Schema, *schema.Schema) { - return schema.Always(), schema.Always() +func (testProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { + return schema.Always(), nil, schema.Always() } -func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (testProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } type swapRotator struct{} -func (swapRotator) Schema() (*schema.Schema, *schema.Schema) { +func (swapRotator) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { inputSchema := schema.Always() + stateSchema := schema.Record(schema.BuilderMap{ + "a": schema.String(), + "b": schema.String(), + }).Schema() outputSchema := schema.Record(schema.BuilderMap{ "a": schema.String(), "b": schema.String(), }).Schema() - return inputSchema, outputSchema + return inputSchema, stateSchema, outputSchema } -func (swapRotator) Open(ctx context.Context, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (swapRotator) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(state), nil } diff --git a/provider.go b/provider.go index 538ef677..5e14fe0e 100644 --- a/provider.go +++ b/provider.go @@ -24,10 +24,10 @@ import ( // other services, etc. type Provider interface { // Schema returns the provider's input and output schemata. - Schema() (inputs, outputs *schema.Schema) + Schema() (inputs, state, outputs *schema.Schema) // Open retrieves the provider's secrets. - Open(ctx context.Context, inputs map[string]Value, executionContext EnvExecContext) (Value, error) + Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error) } // A Rotator enables environments to rotate a secret. @@ -35,9 +35,6 @@ type Provider interface { type Rotator interface { Provider - // Open retrieves the provider's secrets, using persisted state. - Open(ctx context.Context, state map[string]Value, executionContext EnvExecContext) (Value, error) - // Rotate rotates the provider's secret, and returns the rotator's new state to be persisted. Rotate(ctx context.Context, inputs, state map[string]Value) (Value, error) } From 5048c2f33fa96a69ff16a5435a96269dc69764d0 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:18:53 -0800 Subject: [PATCH 20/30] Revert "merge provider and rotator interfaces, pass state param to open" This reverts commit 740e99945b188fda38825e2657c9f664fc02fdbe. --- analysis/common_test.go | 6 +++--- cmd/esc/cli/cli_test.go | 6 +++--- eval/eval.go | 15 +++++---------- eval/eval_test.go | 34 +++++++++++++++------------------- provider.go | 7 +++++-- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/analysis/common_test.go b/analysis/common_test.go index d5305e10..c21d8c13 100644 --- a/analysis/common_test.go +++ b/analysis/common_test.go @@ -53,11 +53,11 @@ var testProviderSchema = schema.Object(). type testProvider struct{} -func (testProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { - return testProviderSchema, nil, schema.Always() +func (testProvider) Schema() (*schema.Schema, *schema.Schema) { + return testProviderSchema, schema.Always() } -func (testProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { +func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } diff --git a/cmd/esc/cli/cli_test.go b/cmd/esc/cli/cli_test.go index a6f45edd..87674f71 100644 --- a/cmd/esc/cli/cli_test.go +++ b/cmd/esc/cli/cli_test.go @@ -198,11 +198,11 @@ func (w *testPulumiWorkspace) GetAccount(key string) (workspace.Account, error) type testProvider struct{} -func (testProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { - return schema.Always(), nil, schema.Always() +func (testProvider) Schema() (*schema.Schema, *schema.Schema) { + return schema.Always(), schema.Always() } -func (testProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { +func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } diff --git a/eval/eval.go b/eval/eval.go index 006ce54a..a940efe2 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -937,7 +937,7 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { if err != nil { e.errorf(repr.syntax(), "%v", err) } else { - inputSchema, _, outputSchema := provider.Schema() + inputSchema, outputSchema := provider.Schema() if err := inputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) } else { @@ -958,7 +958,7 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { return v } - output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), nil, e.execContext) + output, err := provider.Open(e.ctx, inputs.export("").Value.(map[string]esc.Value), e.execContext) if err != nil { e.errorf(repr.syntax(), "%s", err.Error()) v.unknown = true @@ -994,21 +994,17 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { if err != nil { e.errorf(repr.syntax(), "%v", err) } else { - inputSchema, stateSchema, outputSchema := rotator.Schema() + inputSchema, outputSchema := rotator.Schema() if err := inputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) } else { repr.inputSchema = inputSchema } - if err := stateSchema.Compile(); err != nil { - e.errorf(repr.syntax(), "internal error: invalid state schema (%v)", err) - } else { - repr.stateSchema = stateSchema - } if err := outputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid schema (%v)", err) } else { x.schema = outputSchema + repr.stateSchema = outputSchema // state input schema == output schema } } v.schema = x.schema @@ -1033,7 +1029,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { return v } - // todo: validate newState conforms to state schema + // todo: validate newState conforms to output schema e.patchOutputs = append(e.patchOutputs, &Patch{ // rotation output is written back to the fn's `state` input @@ -1047,7 +1043,6 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { output, err := rotator.Open( e.ctx, - inputs.export("").Value.(map[string]esc.Value), state.export("").Value.(map[string]esc.Value), e.execContext, ) diff --git a/eval/eval_test.go b/eval/eval_test.go index 1a2cddbb..bebd46a3 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -43,17 +43,17 @@ func accept() bool { type errorProvider struct{} -func (errorProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { - return schema.Record(schema.BuilderMap{"why": schema.String()}).Schema(), nil, schema.Always() +func (errorProvider) Schema() (*schema.Schema, *schema.Schema) { + return schema.Record(schema.BuilderMap{"why": schema.String()}).Schema(), schema.Always() } -func (errorProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { +func (errorProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.Value{}, errors.New(inputs["why"].Value.(string)) } type testSchemaProvider struct{} -func (testSchemaProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { +func (testSchemaProvider) Schema() (*schema.Schema, *schema.Schema) { s := schema.Object(). Defs(schema.BuilderMap{ "defRecord": schema.Record(schema.BuilderMap{ @@ -107,10 +107,10 @@ func (testSchemaProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Sche }). Schema() - return s, nil, s + return s, s } -func (testSchemaProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { +func (testSchemaProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } @@ -118,41 +118,37 @@ type benchProvider struct { delay time.Duration } -func (benchProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { - return schema.Always(), nil, schema.Always() +func (benchProvider) Schema() (*schema.Schema, *schema.Schema) { + return schema.Always(), schema.Always() } -func (p benchProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { +func (p benchProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { time.Sleep(p.delay) return esc.NewValue(p.delay.String()), nil } type testProvider struct{} -func (testProvider) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { - return schema.Always(), nil, schema.Always() +func (testProvider) Schema() (*schema.Schema, *schema.Schema) { + return schema.Always(), schema.Always() } -func (testProvider) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { +func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(inputs), nil } type swapRotator struct{} -func (swapRotator) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { +func (swapRotator) Schema() (*schema.Schema, *schema.Schema) { inputSchema := schema.Always() - stateSchema := schema.Record(schema.BuilderMap{ - "a": schema.String(), - "b": schema.String(), - }).Schema() outputSchema := schema.Record(schema.BuilderMap{ "a": schema.String(), "b": schema.String(), }).Schema() - return inputSchema, stateSchema, outputSchema + return inputSchema, outputSchema } -func (swapRotator) Open(ctx context.Context, inputs, state map[string]esc.Value, executionContext esc.EnvExecContext) (esc.Value, error) { +func (swapRotator) Open(ctx context.Context, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(state), nil } diff --git a/provider.go b/provider.go index 5e14fe0e..538ef677 100644 --- a/provider.go +++ b/provider.go @@ -24,10 +24,10 @@ import ( // other services, etc. type Provider interface { // Schema returns the provider's input and output schemata. - Schema() (inputs, state, outputs *schema.Schema) + Schema() (inputs, outputs *schema.Schema) // Open retrieves the provider's secrets. - Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error) + Open(ctx context.Context, inputs map[string]Value, executionContext EnvExecContext) (Value, error) } // A Rotator enables environments to rotate a secret. @@ -35,6 +35,9 @@ type Provider interface { type Rotator interface { Provider + // Open retrieves the provider's secrets, using persisted state. + Open(ctx context.Context, state map[string]Value, executionContext EnvExecContext) (Value, error) + // Rotate rotates the provider's secret, and returns the rotator's new state to be persisted. Rotate(ctx context.Context, inputs, state map[string]Value) (Value, error) } From dc8628562d4f20c4ea923eb2e80af6d75d6e1d00 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:27:36 -0800 Subject: [PATCH 21/30] split provider and rotator interfaces, extend ProviderLoader to load both --- analysis/common_test.go | 4 ++++ cmd/esc/cli/cli_test.go | 4 ++++ eval/eval.go | 16 +++------------- eval/eval_test.go | 8 +++++++- provider.go | 5 +++-- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/analysis/common_test.go b/analysis/common_test.go index c21d8c13..3e6dfd27 100644 --- a/analysis/common_test.go +++ b/analysis/common_test.go @@ -71,6 +71,10 @@ func (testProviders) LoadProvider(ctx context.Context, name string) (esc.Provide return nil, fmt.Errorf("unknown provider %q", name) } +func (testProviders) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { + return nil, fmt.Errorf("unknown rotator %q", name) +} + type testEnvironments struct{} func (testEnvironments) LoadEnvironment(ctx context.Context, name string) ([]byte, eval.Decrypter, error) { diff --git a/cmd/esc/cli/cli_test.go b/cmd/esc/cli/cli_test.go index 87674f71..53ec1a49 100644 --- a/cmd/esc/cli/cli_test.go +++ b/cmd/esc/cli/cli_test.go @@ -215,6 +215,10 @@ func (testProviders) LoadProvider(ctx context.Context, name string) (esc.Provide return nil, fmt.Errorf("unknown provider %q", name) } +func (testProviders) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { + return nil, fmt.Errorf("unknown rotator %q", name) +} + type rot128 struct{} func (rot128) Encrypt(_ context.Context, plaintext []byte) ([]byte, error) { diff --git a/eval/eval.go b/eval/eval.go index a940efe2..a27f1c6d 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -37,6 +37,8 @@ import ( type ProviderLoader interface { // LoadProvider loads the provider with the given name. LoadProvider(ctx context.Context, name string) (esc.Provider, error) + // LoadRotator loads the rotator with the given name. + LoadRotator(ctx context.Context, name string) (esc.Rotator, error) } // An EnvironmentLoader provides the environment evaluator the capability to load imported environment definitions. @@ -967,18 +969,6 @@ func (e *evalContext) evaluateBuiltinOpen(x *expr, repr *openExpr) *value { return unexport(output, x) } -func loadRotator(ctx context.Context, providers ProviderLoader, name string) (esc.Rotator, error) { - provider, err := providers.LoadProvider(ctx, name) - if err != nil { - return nil, err - } - rotator, ok := provider.(esc.Rotator) - if !ok { - return nil, fmt.Errorf("provider is not a rotator") - } - return rotator, nil -} - // evaluateBuiltinOpen evaluates a call to the fn::rotate builtin. func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { v := &value{def: x} @@ -990,7 +980,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { return v } - rotator, err := loadRotator(e.ctx, e.providers, repr.node.Provider.GetValue()) + rotator, err := e.providers.LoadRotator(e.ctx, repr.node.Provider.GetValue()) if err != nil { e.errorf(repr.syntax(), "%v", err) } else { diff --git a/eval/eval_test.go b/eval/eval_test.go index bebd46a3..2413d0be 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -174,10 +174,16 @@ func (tp testProviders) LoadProvider(ctx context.Context, name string) (esc.Prov return testProvider{}, nil case "bench": return benchProvider{delay: tp.benchDelay}, nil + } + return nil, fmt.Errorf("unknown provider %q", name) +} + +func (testProviders) LoadRotator(ctx context.Context, name string) (esc.Rotator, error) { + switch name { case "swap": return swapRotator{}, nil } - return nil, fmt.Errorf("unknown provider %q", name) + return nil, fmt.Errorf("unknown rotator %q", name) } type testEnvironments struct { diff --git a/provider.go b/provider.go index 538ef677..9ebc5a1d 100644 --- a/provider.go +++ b/provider.go @@ -33,9 +33,10 @@ type Provider interface { // A Rotator enables environments to rotate a secret. // Rotators persist internal state by writing back to the environment. type Rotator interface { - Provider + // Schema returns the rotator's input and output/state schemata. + Schema() (inputs, outputs *schema.Schema) - // Open retrieves the provider's secrets, using persisted state. + // Open retrieves the rotator's secrets, using persisted state. Open(ctx context.Context, state map[string]Value, executionContext EnvExecContext) (Value, error) // Rotate rotates the provider's secret, and returns the rotator's new state to be persisted. From 1a386a867cb796d36d4fb079cbbd0b5d0a487b6f Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:40:38 -0800 Subject: [PATCH 22/30] pass inputs to Rotator.Open --- eval/eval.go | 11 ++++++++--- eval/eval_test.go | 10 +++++++--- provider.go | 6 +++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/eval/eval.go b/eval/eval.go index a27f1c6d..1ed71b72 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -984,17 +984,21 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { if err != nil { e.errorf(repr.syntax(), "%v", err) } else { - inputSchema, outputSchema := rotator.Schema() + inputSchema, stateSchema, outputSchema := rotator.Schema() if err := inputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid input schema (%v)", err) } else { repr.inputSchema = inputSchema } + if err := stateSchema.Compile(); err != nil { + e.errorf(repr.syntax(), "internal error: invalid state schema (%v)", err) + } else { + repr.stateSchema = stateSchema + } if err := outputSchema.Compile(); err != nil { e.errorf(repr.syntax(), "internal error: invalid schema (%v)", err) } else { x.schema = outputSchema - repr.stateSchema = outputSchema // state input schema == output schema } } v.schema = x.schema @@ -1019,7 +1023,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { return v } - // todo: validate newState conforms to output schema + // todo: validate newState conforms to state schema e.patchOutputs = append(e.patchOutputs, &Patch{ // rotation output is written back to the fn's `state` input @@ -1033,6 +1037,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { output, err := rotator.Open( e.ctx, + inputs.export("").Value.(map[string]esc.Value), state.export("").Value.(map[string]esc.Value), e.execContext, ) diff --git a/eval/eval_test.go b/eval/eval_test.go index 2413d0be..65daeda7 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -139,16 +139,20 @@ func (testProvider) Open(ctx context.Context, inputs map[string]esc.Value, conte type swapRotator struct{} -func (swapRotator) Schema() (*schema.Schema, *schema.Schema) { +func (swapRotator) Schema() (*schema.Schema, *schema.Schema, *schema.Schema) { inputSchema := schema.Always() + stateSchema := schema.Record(schema.BuilderMap{ + "a": schema.String(), + "b": schema.String(), + }).Schema() outputSchema := schema.Record(schema.BuilderMap{ "a": schema.String(), "b": schema.String(), }).Schema() - return inputSchema, outputSchema + return inputSchema, stateSchema, outputSchema } -func (swapRotator) Open(ctx context.Context, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { +func (swapRotator) Open(ctx context.Context, inputs, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { return esc.NewValue(state), nil } diff --git a/provider.go b/provider.go index 9ebc5a1d..6dcfbb46 100644 --- a/provider.go +++ b/provider.go @@ -33,11 +33,11 @@ type Provider interface { // A Rotator enables environments to rotate a secret. // Rotators persist internal state by writing back to the environment. type Rotator interface { - // Schema returns the rotator's input and output/state schemata. - Schema() (inputs, outputs *schema.Schema) + // Schema returns the rotator's input, state, and output schemata. + Schema() (inputs, state, outputs *schema.Schema) // Open retrieves the rotator's secrets, using persisted state. - Open(ctx context.Context, state map[string]Value, executionContext EnvExecContext) (Value, error) + Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error) // Rotate rotates the provider's secret, and returns the rotator's new state to be persisted. Rotate(ctx context.Context, inputs, state map[string]Value) (Value, error) From 53ccc3c4d0c992512b41655479715344fbab5aae Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:37:58 -0800 Subject: [PATCH 23/30] rename fn::rotate provider -> rotator --- ast/expr.go | 34 +-- eval/eval.go | 6 +- eval/expr.go | 18 +- eval/rotate_example_test.go | 8 +- eval/testdata/eval/rotate/env.yaml | 2 +- eval/testdata/eval/rotate/expected.json | 304 ++++++++++++------------ 6 files changed, 186 insertions(+), 186 deletions(-) diff --git a/ast/expr.go b/ast/expr.go index 4a5dc590..cc54d89a 100644 --- a/ast/expr.go +++ b/ast/expr.go @@ -439,32 +439,32 @@ func Open(provider string, inputs *ObjectExpr) *OpenExpr { type RotateExpr struct { builtinNode - Provider *StringExpr - Inputs Expr - State *ObjectExpr + Rotator *StringExpr + Inputs Expr + State *ObjectExpr } -func RotateSyntax(node *syntax.ObjectNode, name *StringExpr, args Expr, provider *StringExpr, inputs Expr, state *ObjectExpr) *RotateExpr { +func RotateSyntax(node *syntax.ObjectNode, name *StringExpr, args Expr, rotator *StringExpr, inputs Expr, state *ObjectExpr) *RotateExpr { return &RotateExpr{ builtinNode: builtin(node, name, args), - Provider: provider, + Rotator: rotator, Inputs: inputs, State: state, } } -func Rotate(provider string, inputs, state *ObjectExpr) *RotateExpr { - name, providerX := String("fn::rotate"), String(provider) +func Rotate(rotator string, inputs, state *ObjectExpr) *RotateExpr { + name, rotatorX := String("fn::rotate"), String(rotator) entries := []ObjectProperty{ - {Key: String("provider"), Value: providerX}, + {Key: String("rotator"), Value: rotatorX}, {Key: String("inputs"), Value: inputs}, {Key: String("state"), Value: state}, } return &RotateExpr{ builtinNode: builtin(nil, name, Object(entries...)), - Provider: providerX, + Rotator: rotatorX, Inputs: inputs, State: state, } @@ -745,15 +745,15 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy return RotateSyntax(node, name, args, nil, nil, nil), diags } - var providerExpr, inputs, stateExpr Expr + var rotatorExpr, inputs, stateExpr Expr var diags syntax.Diagnostics for i := 0; i < len(obj.Entries); i++ { kvp := obj.Entries[i] key := kvp.Key switch key.GetValue() { - case "provider": - providerExpr = kvp.Value + case "rotator": + rotatorExpr = kvp.Value case "inputs": inputs = kvp.Value case "state": @@ -761,17 +761,17 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy } } - provider, ok := providerExpr.(*StringExpr) + provider, ok := rotatorExpr.(*StringExpr) if !ok { - if providerExpr == nil { - diags.Extend(ExprError(obj, "missing provider name ('provider')")) + if rotatorExpr == nil { + diags.Extend(ExprError(obj, "missing rotator name ('rotator')")) } else { - diags.Extend(ExprError(providerExpr, "provider name must be a string literal")) + diags.Extend(ExprError(rotatorExpr, "rotator name must be a string literal")) } } if inputs == nil { - diags.Extend(ExprError(obj, "missing provider inputs ('inputs')")) + diags.Extend(ExprError(obj, "missing rotator inputs ('inputs')")) } state, ok := stateExpr.(*ObjectExpr) diff --git a/eval/eval.go b/eval/eval.go index 1ed71b72..89cc7604 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -329,7 +329,7 @@ func declare[Expr exprNode](e *evalContext, path string, x Expr, base *value) *e case *ast.RotateExpr: repr := &rotateExpr{ node: x, - provider: declare(e, "", x.Provider, nil), + rotator: declare(e, "", x.Rotator, nil), inputs: declare(e, "", x.Inputs, nil), state: declare(e, "", x.State, nil), inputSchema: schema.Always().Schema(), @@ -974,13 +974,13 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { v := &value{def: x} // Can happen if there are parse errors. - if repr.node.Provider == nil { + if repr.node.Rotator == nil { v.schema = schema.Always() v.unknown = true return v } - rotator, err := e.providers.LoadRotator(e.ctx, repr.node.Provider.GetValue()) + rotator, err := e.providers.LoadRotator(e.ctx, repr.node.Rotator.GetValue()) if err != nil { e.errorf(repr.syntax(), "%v", err) } else { diff --git a/eval/expr.go b/eval/expr.go index b793eb6d..3e0e0087 100644 --- a/eval/expr.go +++ b/eval/expr.go @@ -212,15 +212,15 @@ func (x *expr) export(environment string) esc.Expr { Name: name, NameRange: convertRange(repr.node.Name().Syntax().Syntax().Range(), environment), ArgSchema: schema.Record(schema.SchemaMap{ - "provider": schema.String().Schema(), - "inputs": repr.inputSchema, - "state": repr.stateSchema, + "rotator": schema.String().Schema(), + "inputs": repr.inputSchema, + "state": repr.stateSchema, }).Schema(), Arg: esc.Expr{ Object: map[string]esc.Expr{ - "provider": repr.provider.export(environment), - "inputs": repr.inputs.export(environment), - "state": repr.state.export(environment), + "rotator": repr.rotator.export(environment), + "inputs": repr.inputs.export(environment), + "state": repr.state.export(environment), }, }, } @@ -410,9 +410,9 @@ func (x *openExpr) syntax() ast.Expr { type rotateExpr struct { node *ast.RotateExpr - provider *expr - inputs *expr - state *expr + rotator *expr + inputs *expr + state *expr inputSchema *schema.Schema stateSchema *schema.Schema diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index c464abf8..d4391574 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -27,7 +27,7 @@ values: a: a: fn::rotate: - provider: swap + rotator: swap inputs: foo: bar state: @@ -63,15 +63,15 @@ values: // values: // a: // a: - // fn::open: - // provider: swap + // fn::rotate: + // rotator: swap // inputs: // state: // a: b // b: a // b: // - c: - // fn::open::swap: + // fn::rotate::swap: // state: // a: b // b: diff --git a/eval/testdata/eval/rotate/env.yaml b/eval/testdata/eval/rotate/env.yaml index 780d38d9..43269608 100644 --- a/eval/testdata/eval/rotate/env.yaml +++ b/eval/testdata/eval/rotate/env.yaml @@ -1,7 +1,7 @@ values: full: fn::rotate: - provider: swap + rotator: swap inputs: {} state: a: bar1 diff --git a/eval/testdata/eval/rotate/expected.json b/eval/testdata/eval/rotate/expected.json index c93a663d..ee514508 100644 --- a/eval/testdata/eval/rotate/expected.json +++ b/eval/testdata/eval/rotate/expected.json @@ -12,7 +12,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -48,7 +48,7 @@ "argSchema": { "properties": { "inputs": true, - "provider": { + "rotator": { "type": "string" }, "state": { @@ -70,7 +70,7 @@ "type": "object", "required": [ "inputs", - "provider", + "rotator", "state" ] }, @@ -94,30 +94,30 @@ "begin": { "line": 5, "column": 15, - "byte": 67 + "byte": 66 }, "end": { "line": 5, "column": 15, - "byte": 67 + "byte": 66 } }, "schema": { "type": "object" } }, - "provider": { + "rotator": { "range": { "environment": "rotate", "begin": { "line": 4, - "column": 17, - "byte": 48 + "column": 16, + "byte": 47 }, "end": { "line": 4, - "column": 21, - "byte": 52 + "column": 20, + "byte": 51 } }, "schema": { @@ -132,12 +132,12 @@ "begin": { "line": 7, "column": 9, - "byte": 91 + "byte": 90 }, "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -163,12 +163,12 @@ "begin": { "line": 7, "column": 9, - "byte": 91 + "byte": 90 }, "end": { "line": 7, "column": 10, - "byte": 92 + "byte": 91 } }, "b": { @@ -176,12 +176,12 @@ "begin": { "line": 8, "column": 9, - "byte": 107 + "byte": 106 }, "end": { "line": 8, "column": 10, - "byte": 108 + "byte": 107 } } }, @@ -192,12 +192,12 @@ "begin": { "line": 7, "column": 12, - "byte": 94 + "byte": 93 }, "end": { "line": 7, "column": 16, - "byte": 98 + "byte": 97 } }, "schema": { @@ -212,12 +212,12 @@ "begin": { "line": 8, "column": 12, - "byte": 110 + "byte": 109 }, "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -318,7 +318,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } }, @@ -376,12 +376,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } }, @@ -426,12 +426,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -456,12 +456,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 10, "column": 21, - "byte": 144 + "byte": 143 } }, "argSchema": { @@ -493,12 +493,12 @@ "begin": { "line": 11, "column": 7, - "byte": 152 + "byte": 151 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -511,12 +511,12 @@ "begin": { "line": 12, "column": 9, - "byte": 167 + "byte": 166 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -542,12 +542,12 @@ "begin": { "line": 12, "column": 9, - "byte": 167 + "byte": 166 }, "end": { "line": 12, "column": 10, - "byte": 168 + "byte": 167 } }, "b": { @@ -555,12 +555,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 182 }, "end": { "line": 13, "column": 10, - "byte": 184 + "byte": 183 } } }, @@ -571,12 +571,12 @@ "begin": { "line": 12, "column": 12, - "byte": 170 + "byte": 169 }, "end": { "line": 12, "column": 16, - "byte": 174 + "byte": 173 } }, "schema": { @@ -591,12 +591,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 185 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -626,7 +626,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -677,12 +677,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -948,7 +948,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -986,7 +986,7 @@ "argSchema": { "properties": { "inputs": true, - "provider": { + "rotator": { "type": "string" }, "state": { @@ -1008,7 +1008,7 @@ "type": "object", "required": [ "inputs", - "provider", + "rotator", "state" ] }, @@ -1032,30 +1032,30 @@ "begin": { "line": 5, "column": 15, - "byte": 67 + "byte": 66 }, "end": { "line": 5, "column": 15, - "byte": 67 + "byte": 66 } }, "schema": { "type": "object" } }, - "provider": { + "rotator": { "range": { "environment": "rotate", "begin": { "line": 4, - "column": 17, - "byte": 48 + "column": 16, + "byte": 47 }, "end": { "line": 4, - "column": 21, - "byte": 52 + "column": 20, + "byte": 51 } }, "schema": { @@ -1070,12 +1070,12 @@ "begin": { "line": 7, "column": 9, - "byte": 91 + "byte": 90 }, "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -1101,12 +1101,12 @@ "begin": { "line": 7, "column": 9, - "byte": 91 + "byte": 90 }, "end": { "line": 7, "column": 10, - "byte": 92 + "byte": 91 } }, "b": { @@ -1114,12 +1114,12 @@ "begin": { "line": 8, "column": 9, - "byte": 107 + "byte": 106 }, "end": { "line": 8, "column": 10, - "byte": 108 + "byte": 107 } } }, @@ -1130,12 +1130,12 @@ "begin": { "line": 7, "column": 12, - "byte": 94 + "byte": 93 }, "end": { "line": 7, "column": 16, - "byte": 98 + "byte": 97 } }, "schema": { @@ -1150,12 +1150,12 @@ "begin": { "line": 8, "column": 12, - "byte": 110 + "byte": 109 }, "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -1256,7 +1256,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } }, @@ -1285,7 +1285,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -1314,12 +1314,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } }, @@ -1343,12 +1343,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -1364,12 +1364,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -1396,12 +1396,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 10, "column": 21, - "byte": 144 + "byte": 143 } }, "argSchema": { @@ -1433,12 +1433,12 @@ "begin": { "line": 11, "column": 7, - "byte": 152 + "byte": 151 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -1451,12 +1451,12 @@ "begin": { "line": 12, "column": 9, - "byte": 167 + "byte": 166 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -1482,12 +1482,12 @@ "begin": { "line": 12, "column": 9, - "byte": 167 + "byte": 166 }, "end": { "line": 12, "column": 10, - "byte": 168 + "byte": 167 } }, "b": { @@ -1495,12 +1495,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 182 }, "end": { "line": 13, "column": 10, - "byte": 184 + "byte": 183 } } }, @@ -1511,12 +1511,12 @@ "begin": { "line": 12, "column": 12, - "byte": 170 + "byte": 169 }, "end": { "line": 12, "column": 16, - "byte": 174 + "byte": 173 } }, "schema": { @@ -1531,12 +1531,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 185 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -1568,7 +1568,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -1586,7 +1586,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -1603,7 +1603,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -1655,12 +1655,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -1673,12 +1673,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -1690,12 +1690,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -1984,7 +1984,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -2022,7 +2022,7 @@ "argSchema": { "properties": { "inputs": true, - "provider": { + "rotator": { "type": "string" }, "state": { @@ -2044,7 +2044,7 @@ "type": "object", "required": [ "inputs", - "provider", + "rotator", "state" ] }, @@ -2068,30 +2068,30 @@ "begin": { "line": 5, "column": 15, - "byte": 67 + "byte": 66 }, "end": { "line": 5, "column": 15, - "byte": 67 + "byte": 66 } }, "schema": { "type": "object" } }, - "provider": { + "rotator": { "range": { "environment": "rotate", "begin": { "line": 4, - "column": 17, - "byte": 48 + "column": 16, + "byte": 47 }, "end": { "line": 4, - "column": 21, - "byte": 52 + "column": 20, + "byte": 51 } }, "schema": { @@ -2106,12 +2106,12 @@ "begin": { "line": 7, "column": 9, - "byte": 91 + "byte": 90 }, "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -2137,12 +2137,12 @@ "begin": { "line": 7, "column": 9, - "byte": 91 + "byte": 90 }, "end": { "line": 7, "column": 10, - "byte": 92 + "byte": 91 } }, "b": { @@ -2150,12 +2150,12 @@ "begin": { "line": 8, "column": 9, - "byte": 107 + "byte": 106 }, "end": { "line": 8, "column": 10, - "byte": 108 + "byte": 107 } } }, @@ -2166,12 +2166,12 @@ "begin": { "line": 7, "column": 12, - "byte": 94 + "byte": 93 }, "end": { "line": 7, "column": 16, - "byte": 98 + "byte": 97 } }, "schema": { @@ -2186,12 +2186,12 @@ "begin": { "line": 8, "column": 12, - "byte": 110 + "byte": 109 }, "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } }, "schema": { @@ -2292,7 +2292,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } }, @@ -2321,7 +2321,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -2350,12 +2350,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } }, @@ -2379,12 +2379,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -2400,12 +2400,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -2432,12 +2432,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 10, "column": 21, - "byte": 144 + "byte": 143 } }, "argSchema": { @@ -2469,12 +2469,12 @@ "begin": { "line": 11, "column": 7, - "byte": 152 + "byte": 151 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -2487,12 +2487,12 @@ "begin": { "line": 12, "column": 9, - "byte": 167 + "byte": 166 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -2518,12 +2518,12 @@ "begin": { "line": 12, "column": 9, - "byte": 167 + "byte": 166 }, "end": { "line": 12, "column": 10, - "byte": 168 + "byte": 167 } }, "b": { @@ -2531,12 +2531,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 182 }, "end": { "line": 13, "column": 10, - "byte": 184 + "byte": 183 } } }, @@ -2547,12 +2547,12 @@ "begin": { "line": 12, "column": 12, - "byte": 170 + "byte": 169 }, "end": { "line": 12, "column": 16, - "byte": 174 + "byte": 173 } }, "schema": { @@ -2567,12 +2567,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 185 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } }, "schema": { @@ -2604,7 +2604,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -2622,7 +2622,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -2639,7 +2639,7 @@ "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -2691,12 +2691,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -2709,12 +2709,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -2726,12 +2726,12 @@ "begin": { "line": 10, "column": 5, - "byte": 128 + "byte": 127 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -3007,12 +3007,12 @@ "begin": { "line": 8, "column": 12, - "byte": 110 + "byte": 109 }, "end": { "line": 8, "column": 16, - "byte": 114 + "byte": 113 } } } @@ -3025,12 +3025,12 @@ "begin": { "line": 7, "column": 12, - "byte": 94 + "byte": 93 }, "end": { "line": 7, "column": 16, - "byte": 98 + "byte": 97 } } } @@ -3064,12 +3064,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 185 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 189 } } } @@ -3082,12 +3082,12 @@ "begin": { "line": 12, "column": 12, - "byte": 170 + "byte": 169 }, "end": { "line": 12, "column": 16, - "byte": 174 + "byte": 173 } } } From befdf5f7d7651354f78802e919bc5969cb554eff Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:11:43 -0800 Subject: [PATCH 24/30] rename ApplyPatches -> ApplyValuePatches --- eval/patch.go | 4 ++-- eval/rotate_example_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eval/patch.go b/eval/patch.go index b3bdc4fe..59f6464c 100644 --- a/eval/patch.go +++ b/eval/patch.go @@ -27,9 +27,9 @@ type Patch struct { Replacement esc.Value } -// ApplyPatches applies a set of patches values to an environment definition. +// ApplyValuePatches applies a set of patches values to an environment definition. // If patch values contain secret values, they will be wrapped with fn::secret. -func ApplyPatches(source []byte, patches []*Patch) ([]byte, error) { +func ApplyValuePatches(source []byte, patches []*Patch) ([]byte, error) { var doc yaml.Node if err := yaml.Unmarshal(source, &doc); err != nil { return nil, err diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index d4391574..b6627754 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -53,7 +53,7 @@ values: require.Len(t, diags, 0) // writeback state patches - update, err := ApplyPatches([]byte(def), patches) + update, err := ApplyValuePatches([]byte(def), patches) require.NoError(t, err) encryptedYaml, err := EncryptSecrets(context.Background(), "", update, rot128{}) From 53dc66cb807b43ffb7bc797dff6944e6959251bd Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:04:14 -0800 Subject: [PATCH 25/30] do not intermix state and inputs for shortform fn::rotate --- ast/expr.go | 35 +- eval/expr.go | 21 +- eval/rotate_example_test.go | 65 ++-- eval/testdata/eval/rotate/env.yaml | 1 + eval/testdata/eval/rotate/expected.json | 420 +++++++++++++----------- 5 files changed, 298 insertions(+), 244 deletions(-) diff --git a/ast/expr.go b/ast/expr.go index cc54d89a..a146679b 100644 --- a/ast/expr.go +++ b/ast/expr.go @@ -18,7 +18,6 @@ import ( "encoding/json" "fmt" "reflect" - "slices" "strings" "github.com/hashicorp/hcl/v2" @@ -784,30 +783,38 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy func parseShortRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { kvp := node.Index(0) - provider := StringSyntaxValue(name.Syntax().(*syntax.StringNode), strings.TrimPrefix(kvp.Key.Value(), "fn::rotate::")) + rotator := StringSyntaxValue(name.Syntax().(*syntax.StringNode), strings.TrimPrefix(kvp.Key.Value(), "fn::rotate::")) - inputs, ok := args.(*ObjectExpr) + obj, ok := args.(*ObjectExpr) if !ok { - diags := syntax.Diagnostics{ExprError(args, "provider inputs must be an object")} + diags := syntax.Diagnostics{ExprError(args, "the argument to fn::rotate must be an object containing 'inputs' and 'state'")} return RotateSyntax(node, name, args, nil, nil, nil), diags } - // hoist 'state' key out of inputs - var stateExpr Expr - if i := slices.IndexFunc(inputs.Entries, func(kvp ObjectProperty) bool { - return kvp.Key.GetValue() == "state" - }); i != -1 { - stateExpr = inputs.Entries[i].Value - inputs.Entries = slices.Delete(inputs.Entries, i, i+1) + var inputs, stateExpr Expr + var diags syntax.Diagnostics + + for i := 0; i < len(obj.Entries); i++ { + kvp := obj.Entries[i] + key := kvp.Key + switch key.GetValue() { + case "inputs": + inputs = kvp.Value + case "state": + stateExpr = kvp.Value + } + } + + if inputs == nil { + diags.Extend(ExprError(obj, "missing rotator inputs ('inputs')")) } state, ok := stateExpr.(*ObjectExpr) if !ok && state != nil { - diags := syntax.Diagnostics{ExprError(stateExpr, "rotation state must be an object literal")} - return RotateSyntax(node, name, args, nil, nil, nil), diags + diags.Extend(ExprError(stateExpr, "rotation state must be an object literal")) } - return RotateSyntax(node, name, args, provider, inputs, state), nil + return RotateSyntax(node, name, args, rotator, inputs, state), nil } func parseJoin(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { diff --git a/eval/expr.go b/eval/expr.go index 3e0e0087..237133b3 100644 --- a/eval/expr.go +++ b/eval/expr.go @@ -16,8 +16,6 @@ package eval import ( "fmt" - "maps" - "github.com/hashicorp/hcl/v2" "github.com/pulumi/esc" "github.com/pulumi/esc/ast" @@ -225,18 +223,19 @@ func (x *expr) export(environment string) esc.Expr { }, } } else { - argMap := schema.SchemaMap{} - maps.Copy(argMap, repr.inputSchema.Properties) - argMap["state"] = repr.stateSchema - - arg := repr.inputs.export(environment) - arg.Object["state"] = repr.state.export(environment) - ex.Builtin = &esc.BuiltinExpr{ Name: name, NameRange: convertRange(repr.node.Name().Syntax().Syntax().Range(), environment), - ArgSchema: schema.Record(argMap).Schema(), - Arg: arg, + ArgSchema: schema.Record(schema.SchemaMap{ + "inputs": repr.inputSchema, + "state": repr.stateSchema, + }).Schema(), + Arg: esc.Expr{ + Object: map[string]esc.Expr{ + "inputs": repr.inputs.export(environment), + "state": repr.state.export(environment), + }, + }, } } case *secretExpr: diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index b6627754..ea8e8885 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -16,12 +16,11 @@ package eval import ( "context" + "fmt" "github.com/pulumi/esc" - "github.com/stretchr/testify/require" - "testing" ) -func TestExampleRotate(t *testing.T) { +func ExampleRotate() { const def = ` values: a: @@ -36,45 +35,45 @@ values: b: - c: fn::rotate::swap: - foo: bar + inputs: + foo: bar state: a: fn::secret: a b: b ` - env, diags, err := LoadYAMLBytes("", []byte(def)) - require.NoError(t, err) - require.Len(t, diags, 0) + env, _, _ := LoadYAMLBytes("", []byte(def)) // rotate the environment - execContext, err := esc.NewExecContext(nil) - require.NoError(t, err) - _, patches, diags := RotateEnvironment(context.Background(), "", env, rot128{}, testProviders{}, &testEnvironments{}, execContext) - require.Len(t, diags, 0) + execContext, _ := esc.NewExecContext(nil) + _, patches, _ := RotateEnvironment(context.Background(), "", env, rot128{}, testProviders{}, &testEnvironments{}, execContext) // writeback state patches - update, err := ApplyValuePatches([]byte(def), patches) - require.NoError(t, err) + updated, _ := ApplyValuePatches([]byte(def), patches) - encryptedYaml, err := EncryptSecrets(context.Background(), "", update, rot128{}) - require.NoError(t, err) + // encrypt secret values + encryptedYaml, _ := EncryptSecrets(context.Background(), "", updated, rot128{}) - t.Log(string(encryptedYaml)) - // values: - // a: - // a: - // fn::rotate: - // rotator: swap - // inputs: - // state: - // a: b - // b: a - // b: - // - c: - // fn::rotate::swap: - // state: - // a: b - // b: - // fn::secret: - // ciphertext: ZXNjeAAAAAHhQRt8TQ== + fmt.Println(string(encryptedYaml)) + // Output: + // values: + // a: + // a: + // fn::rotate: + // rotator: swap + // inputs: + // foo: bar + // state: + // a: b + // b: a + // b: + // - c: + // fn::rotate::swap: + // inputs: + // foo: bar + // state: + // a: b + // b: + // fn::secret: + // ciphertext: ZXNjeAAAAAHhQRt8TQ== } diff --git a/eval/testdata/eval/rotate/env.yaml b/eval/testdata/eval/rotate/env.yaml index 43269608..a6238978 100644 --- a/eval/testdata/eval/rotate/env.yaml +++ b/eval/testdata/eval/rotate/env.yaml @@ -8,6 +8,7 @@ values: b: bar2 short: fn::rotate::swap: + inputs: {} state: a: bar1 b: bar2 diff --git a/eval/testdata/eval/rotate/expected.json b/eval/testdata/eval/rotate/expected.json index ee514508..ef314396 100644 --- a/eval/testdata/eval/rotate/expected.json +++ b/eval/testdata/eval/rotate/expected.json @@ -236,12 +236,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -261,12 +261,12 @@ "test": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 9, "byte": 0 } @@ -277,12 +277,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -298,12 +298,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 13, "byte": 2 }, "end": { - "line": 16, + "line": 17, "column": 17, "byte": 6 } @@ -327,12 +327,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 17, "byte": 6 }, "end": { - "line": 16, + "line": 17, "column": 19, "byte": 8 } @@ -340,12 +340,12 @@ "value": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -361,12 +361,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 23, "byte": 12 }, "end": { - "line": 16, + "line": 17, "column": 28, "byte": 17 } @@ -379,9 +379,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } }, @@ -390,12 +390,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 28, "byte": 17 }, "end": { - "line": 16, + "line": 17, "column": 30, "byte": 19 } @@ -403,12 +403,12 @@ "value": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -429,9 +429,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -466,6 +466,7 @@ }, "argSchema": { "properties": { + "inputs": true, "state": { "properties": { "a": { @@ -484,39 +485,54 @@ }, "type": "object", "required": [ + "inputs", "state" ] }, "arg": { "range": { - "environment": "rotate", "begin": { - "line": 11, - "column": 7, - "byte": 151 + "line": 0, + "column": 0, + "byte": 0 }, "end": { - "line": 13, - "column": 16, - "byte": 189 + "line": 0, + "column": 0, + "byte": 0 } }, - "schema": { - "type": "object" - }, "object": { + "inputs": { + "range": { + "environment": "rotate", + "begin": { + "line": 11, + "column": 15, + "byte": 159 + }, + "end": { + "line": 11, + "column": 15, + "byte": 159 + } + }, + "schema": { + "type": "object" + } + }, "state": { "range": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 9, - "byte": 166 + "byte": 183 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -540,27 +556,27 @@ "a": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 9, - "byte": 166 + "byte": 183 }, "end": { - "line": 12, + "line": 13, "column": 10, - "byte": 167 + "byte": 184 } }, "b": { "environment": "rotate", "begin": { - "line": 13, + "line": 14, "column": 9, - "byte": 182 + "byte": 199 }, "end": { - "line": 13, + "line": 14, "column": 10, - "byte": 183 + "byte": 200 } } }, @@ -569,14 +585,14 @@ "range": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 12, - "byte": 169 + "byte": 186 }, "end": { - "line": 12, + "line": 13, "column": 16, - "byte": 173 + "byte": 190 } }, "schema": { @@ -589,14 +605,14 @@ "range": { "environment": "rotate", "begin": { - "line": 13, + "line": 14, "column": 12, - "byte": 185 + "byte": 202 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -640,12 +656,12 @@ "def": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -657,12 +673,12 @@ "def": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -680,9 +696,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -1174,12 +1190,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -1199,12 +1215,12 @@ "test": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 9, "byte": 0 } @@ -1215,12 +1231,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -1236,12 +1252,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 13, "byte": 2 }, "end": { - "line": 16, + "line": 17, "column": 17, "byte": 6 } @@ -1265,12 +1281,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 17, "byte": 6 }, "end": { - "line": 16, + "line": 17, "column": 19, "byte": 8 } @@ -1299,12 +1315,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 23, "byte": 12 }, "end": { - "line": 16, + "line": 17, "column": 28, "byte": 17 } @@ -1317,9 +1333,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } }, @@ -1328,12 +1344,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 28, "byte": 17 }, "end": { - "line": 16, + "line": 17, "column": 30, "byte": 19 } @@ -1346,9 +1362,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -1367,9 +1383,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -1406,6 +1422,7 @@ }, "argSchema": { "properties": { + "inputs": true, "state": { "properties": { "a": { @@ -1424,39 +1441,54 @@ }, "type": "object", "required": [ + "inputs", "state" ] }, "arg": { "range": { - "environment": "rotate", "begin": { - "line": 11, - "column": 7, - "byte": 151 + "line": 0, + "column": 0, + "byte": 0 }, "end": { - "line": 13, - "column": 16, - "byte": 189 + "line": 0, + "column": 0, + "byte": 0 } }, - "schema": { - "type": "object" - }, "object": { + "inputs": { + "range": { + "environment": "rotate", + "begin": { + "line": 11, + "column": 15, + "byte": 159 + }, + "end": { + "line": 11, + "column": 15, + "byte": 159 + } + }, + "schema": { + "type": "object" + } + }, "state": { "range": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 9, - "byte": 166 + "byte": 183 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -1480,27 +1512,27 @@ "a": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 9, - "byte": 166 + "byte": 183 }, "end": { - "line": 12, + "line": 13, "column": 10, - "byte": 167 + "byte": 184 } }, "b": { "environment": "rotate", "begin": { - "line": 13, + "line": 14, "column": 9, - "byte": 182 + "byte": 199 }, "end": { - "line": 13, + "line": 14, "column": 10, - "byte": 183 + "byte": 200 } } }, @@ -1509,14 +1541,14 @@ "range": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 12, - "byte": 169 + "byte": 186 }, "end": { - "line": 12, + "line": 13, "column": 16, - "byte": 173 + "byte": 190 } }, "schema": { @@ -1529,14 +1561,14 @@ "range": { "environment": "rotate", "begin": { - "line": 13, + "line": 14, "column": 12, - "byte": 185 + "byte": 202 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -1616,12 +1648,12 @@ "def": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -1633,12 +1665,12 @@ "def": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -1658,9 +1690,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -1676,9 +1708,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -1693,9 +1725,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -2210,12 +2242,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -2235,12 +2267,12 @@ "test": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 9, "byte": 0 } @@ -2251,12 +2283,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -2272,12 +2304,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 13, "byte": 2 }, "end": { - "line": 16, + "line": 17, "column": 17, "byte": 6 } @@ -2301,12 +2333,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 17, "byte": 6 }, "end": { - "line": 16, + "line": 17, "column": 19, "byte": 8 } @@ -2335,12 +2367,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 23, "byte": 12 }, "end": { - "line": 16, + "line": 17, "column": 28, "byte": 17 } @@ -2353,9 +2385,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } }, @@ -2364,12 +2396,12 @@ "range": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 28, "byte": 17 }, "end": { - "line": 16, + "line": 17, "column": 30, "byte": 19 } @@ -2382,9 +2414,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -2403,9 +2435,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -2442,6 +2474,7 @@ }, "argSchema": { "properties": { + "inputs": true, "state": { "properties": { "a": { @@ -2460,39 +2493,54 @@ }, "type": "object", "required": [ + "inputs", "state" ] }, "arg": { "range": { - "environment": "rotate", "begin": { - "line": 11, - "column": 7, - "byte": 151 + "line": 0, + "column": 0, + "byte": 0 }, "end": { - "line": 13, - "column": 16, - "byte": 189 + "line": 0, + "column": 0, + "byte": 0 } }, - "schema": { - "type": "object" - }, "object": { + "inputs": { + "range": { + "environment": "rotate", + "begin": { + "line": 11, + "column": 15, + "byte": 159 + }, + "end": { + "line": 11, + "column": 15, + "byte": 159 + } + }, + "schema": { + "type": "object" + } + }, "state": { "range": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 9, - "byte": 166 + "byte": 183 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -2516,27 +2564,27 @@ "a": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 9, - "byte": 166 + "byte": 183 }, "end": { - "line": 12, + "line": 13, "column": 10, - "byte": 167 + "byte": 184 } }, "b": { "environment": "rotate", "begin": { - "line": 13, + "line": 14, "column": 9, - "byte": 182 + "byte": 199 }, "end": { - "line": 13, + "line": 14, "column": 10, - "byte": 183 + "byte": 200 } } }, @@ -2545,14 +2593,14 @@ "range": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 12, - "byte": 169 + "byte": 186 }, "end": { - "line": 12, + "line": 13, "column": 16, - "byte": 173 + "byte": 190 } }, "schema": { @@ -2565,14 +2613,14 @@ "range": { "environment": "rotate", "begin": { - "line": 13, + "line": 14, "column": 12, - "byte": 185 + "byte": 202 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } }, "schema": { @@ -2652,12 +2700,12 @@ "def": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 11, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -2669,12 +2717,12 @@ "def": { "environment": "rotate", "begin": { - "line": 16, + "line": 17, "column": 5, "byte": 0 }, "end": { - "line": 16, + "line": 17, "column": 31, "byte": 0 } @@ -2694,9 +2742,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -2712,9 +2760,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -2729,9 +2777,9 @@ "byte": 127 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -3062,14 +3110,14 @@ "def": { "environment": "rotate", "begin": { - "line": 13, + "line": 14, "column": 12, - "byte": 185 + "byte": 202 }, "end": { - "line": 13, + "line": 14, "column": 16, - "byte": 189 + "byte": 206 } } } @@ -3080,14 +3128,14 @@ "def": { "environment": "rotate", "begin": { - "line": 12, + "line": 13, "column": 12, - "byte": 169 + "byte": 186 }, "end": { - "line": 12, + "line": 13, "column": 16, - "byte": 173 + "byte": 190 } } } From 58732faac3c971152c79dcf44f2092dfb331ee1c Mon Sep 17 00:00:00 2001 From: Claire <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:20:25 -0800 Subject: [PATCH 26/30] Update eval/eval.go Co-authored-by: Derek --- eval/eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eval/eval.go b/eval/eval.go index 89cc7604..7d1a4f74 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -106,7 +106,7 @@ func CheckEnvironment( } // RotateEnvironment evaluates the given environment and invokes provider rotate methods. -// The updated rotation state is returned a set of patches to be written back to the environment. +// The updated rotation state is returned with a set of patches to be written back to the environment. func RotateEnvironment( ctx context.Context, name string, From 1b0e0ddf36aa84ec0afed3be1b91953bc1b755fe Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:24:27 -0800 Subject: [PATCH 27/30] Rotator.Rotate should receive execution context --- eval/eval.go | 1 + eval/eval_test.go | 2 +- provider.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/eval/eval.go b/eval/eval.go index 7d1a4f74..0c3934d6 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -1016,6 +1016,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { e.ctx, inputs.export("").Value.(map[string]esc.Value), state.export("").Value.(map[string]esc.Value), + e.execContext, ) if err != nil { e.errorf(repr.syntax(), "rotate: %s", err.Error()) diff --git a/eval/eval_test.go b/eval/eval_test.go index 65daeda7..5bab3137 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -156,7 +156,7 @@ func (swapRotator) Open(ctx context.Context, inputs, state map[string]esc.Value, return esc.NewValue(state), nil } -func (swapRotator) Rotate(ctx context.Context, inputs, state map[string]esc.Value) (esc.Value, error) { +func (swapRotator) Rotate(ctx context.Context, inputs, state map[string]esc.Value, context esc.EnvExecContext) (esc.Value, error) { newState := esc.NewValue(map[string]esc.Value{ "a": state["b"], "b": state["a"], diff --git a/provider.go b/provider.go index 6dcfbb46..4e0904c5 100644 --- a/provider.go +++ b/provider.go @@ -40,5 +40,5 @@ type Rotator interface { Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error) // Rotate rotates the provider's secret, and returns the rotator's new state to be persisted. - Rotate(ctx context.Context, inputs, state map[string]Value) (Value, error) + Rotate(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error) } From 38343dad289088f458ca9a87cd5ee69007377f6d Mon Sep 17 00:00:00 2001 From: Claire <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:01:40 -0800 Subject: [PATCH 28/30] Update eval/eval.go Co-authored-by: Pat Gavlin --- eval/eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eval/eval.go b/eval/eval.go index 0c3934d6..76d0a185 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -1028,7 +1028,7 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { e.patchOutputs = append(e.patchOutputs, &Patch{ // rotation output is written back to the fn's `state` input - DocPath: x.path + "." + repr.node.Name().GetValue() + ".state", + DocPath: util.JoinKey(x.path, repr.node.Name().GetValue()) + ".state", Replacement: newState, }) From b3fd85d69398d493c8b90ea8d7ea2f9d8f421d61 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:03:59 -0800 Subject: [PATCH 29/30] Revert "rename fn::rotate provider -> rotator" This reverts commit 53ccc3c4d0c992512b41655479715344fbab5aae. --- ast/expr.go | 40 +-- eval/eval.go | 6 +- eval/expr.go | 18 +- eval/rotate_example_test.go | 4 +- eval/testdata/eval/rotate/env.yaml | 2 +- eval/testdata/eval/rotate/expected.json | 308 ++++++++++++------------ 6 files changed, 189 insertions(+), 189 deletions(-) diff --git a/ast/expr.go b/ast/expr.go index a146679b..62a8deec 100644 --- a/ast/expr.go +++ b/ast/expr.go @@ -438,32 +438,32 @@ func Open(provider string, inputs *ObjectExpr) *OpenExpr { type RotateExpr struct { builtinNode - Rotator *StringExpr - Inputs Expr - State *ObjectExpr + Provider *StringExpr + Inputs Expr + State *ObjectExpr } -func RotateSyntax(node *syntax.ObjectNode, name *StringExpr, args Expr, rotator *StringExpr, inputs Expr, state *ObjectExpr) *RotateExpr { +func RotateSyntax(node *syntax.ObjectNode, name *StringExpr, args Expr, provider *StringExpr, inputs Expr, state *ObjectExpr) *RotateExpr { return &RotateExpr{ builtinNode: builtin(node, name, args), - Rotator: rotator, + Provider: provider, Inputs: inputs, State: state, } } -func Rotate(rotator string, inputs, state *ObjectExpr) *RotateExpr { - name, rotatorX := String("fn::rotate"), String(rotator) +func Rotate(provider string, inputs, state *ObjectExpr) *RotateExpr { + name, providerX := String("fn::rotate"), String(provider) entries := []ObjectProperty{ - {Key: String("rotator"), Value: rotatorX}, + {Key: String("provider"), Value: providerX}, {Key: String("inputs"), Value: inputs}, {Key: String("state"), Value: state}, } return &RotateExpr{ builtinNode: builtin(nil, name, Object(entries...)), - Rotator: rotatorX, + Provider: providerX, Inputs: inputs, State: state, } @@ -744,15 +744,15 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy return RotateSyntax(node, name, args, nil, nil, nil), diags } - var rotatorExpr, inputs, stateExpr Expr + var providerExpr, inputs, stateExpr Expr var diags syntax.Diagnostics for i := 0; i < len(obj.Entries); i++ { kvp := obj.Entries[i] key := kvp.Key switch key.GetValue() { - case "rotator": - rotatorExpr = kvp.Value + case "provider": + providerExpr = kvp.Value case "inputs": inputs = kvp.Value case "state": @@ -760,17 +760,17 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy } } - provider, ok := rotatorExpr.(*StringExpr) + provider, ok := providerExpr.(*StringExpr) if !ok { - if rotatorExpr == nil { - diags.Extend(ExprError(obj, "missing rotator name ('rotator')")) + if providerExpr == nil { + diags.Extend(ExprError(obj, "missing provider name ('provider')")) } else { - diags.Extend(ExprError(rotatorExpr, "rotator name must be a string literal")) + diags.Extend(ExprError(providerExpr, "provider name must be a string literal")) } } if inputs == nil { - diags.Extend(ExprError(obj, "missing rotator inputs ('inputs')")) + diags.Extend(ExprError(obj, "missing provider inputs ('inputs')")) } state, ok := stateExpr.(*ObjectExpr) @@ -783,7 +783,7 @@ func parseRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, sy func parseShortRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { kvp := node.Index(0) - rotator := StringSyntaxValue(name.Syntax().(*syntax.StringNode), strings.TrimPrefix(kvp.Key.Value(), "fn::rotate::")) + provider := StringSyntaxValue(name.Syntax().(*syntax.StringNode), strings.TrimPrefix(kvp.Key.Value(), "fn::rotate::")) obj, ok := args.(*ObjectExpr) if !ok { @@ -806,7 +806,7 @@ func parseShortRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Exp } if inputs == nil { - diags.Extend(ExprError(obj, "missing rotator inputs ('inputs')")) + diags.Extend(ExprError(obj, "missing provider inputs ('inputs')")) } state, ok := stateExpr.(*ObjectExpr) @@ -814,7 +814,7 @@ func parseShortRotate(node *syntax.ObjectNode, name *StringExpr, args Expr) (Exp diags.Extend(ExprError(stateExpr, "rotation state must be an object literal")) } - return RotateSyntax(node, name, args, rotator, inputs, state), nil + return RotateSyntax(node, name, args, provider, inputs, state), nil } func parseJoin(node *syntax.ObjectNode, name *StringExpr, args Expr) (Expr, syntax.Diagnostics) { diff --git a/eval/eval.go b/eval/eval.go index 76d0a185..b95c88e8 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -329,7 +329,7 @@ func declare[Expr exprNode](e *evalContext, path string, x Expr, base *value) *e case *ast.RotateExpr: repr := &rotateExpr{ node: x, - rotator: declare(e, "", x.Rotator, nil), + provider: declare(e, "", x.Provider, nil), inputs: declare(e, "", x.Inputs, nil), state: declare(e, "", x.State, nil), inputSchema: schema.Always().Schema(), @@ -974,13 +974,13 @@ func (e *evalContext) evaluateBuiltinRotate(x *expr, repr *rotateExpr) *value { v := &value{def: x} // Can happen if there are parse errors. - if repr.node.Rotator == nil { + if repr.node.Provider == nil { v.schema = schema.Always() v.unknown = true return v } - rotator, err := e.providers.LoadRotator(e.ctx, repr.node.Rotator.GetValue()) + rotator, err := e.providers.LoadRotator(e.ctx, repr.node.Provider.GetValue()) if err != nil { e.errorf(repr.syntax(), "%v", err) } else { diff --git a/eval/expr.go b/eval/expr.go index 237133b3..e54feee7 100644 --- a/eval/expr.go +++ b/eval/expr.go @@ -210,15 +210,15 @@ func (x *expr) export(environment string) esc.Expr { Name: name, NameRange: convertRange(repr.node.Name().Syntax().Syntax().Range(), environment), ArgSchema: schema.Record(schema.SchemaMap{ - "rotator": schema.String().Schema(), - "inputs": repr.inputSchema, - "state": repr.stateSchema, + "provider": schema.String().Schema(), + "inputs": repr.inputSchema, + "state": repr.stateSchema, }).Schema(), Arg: esc.Expr{ Object: map[string]esc.Expr{ - "rotator": repr.rotator.export(environment), - "inputs": repr.inputs.export(environment), - "state": repr.state.export(environment), + "provider": repr.provider.export(environment), + "inputs": repr.inputs.export(environment), + "state": repr.state.export(environment), }, }, } @@ -409,9 +409,9 @@ func (x *openExpr) syntax() ast.Expr { type rotateExpr struct { node *ast.RotateExpr - rotator *expr - inputs *expr - state *expr + provider *expr + inputs *expr + state *expr inputSchema *schema.Schema stateSchema *schema.Schema diff --git a/eval/rotate_example_test.go b/eval/rotate_example_test.go index ea8e8885..4be47b0c 100644 --- a/eval/rotate_example_test.go +++ b/eval/rotate_example_test.go @@ -26,7 +26,7 @@ values: a: a: fn::rotate: - rotator: swap + provider: swap inputs: foo: bar state: @@ -60,7 +60,7 @@ values: // a: // a: // fn::rotate: - // rotator: swap + // provider: swap // inputs: // foo: bar // state: diff --git a/eval/testdata/eval/rotate/env.yaml b/eval/testdata/eval/rotate/env.yaml index a6238978..cb8231e9 100644 --- a/eval/testdata/eval/rotate/env.yaml +++ b/eval/testdata/eval/rotate/env.yaml @@ -1,7 +1,7 @@ values: full: fn::rotate: - rotator: swap + provider: swap inputs: {} state: a: bar1 diff --git a/eval/testdata/eval/rotate/expected.json b/eval/testdata/eval/rotate/expected.json index ef314396..2a1ee0ea 100644 --- a/eval/testdata/eval/rotate/expected.json +++ b/eval/testdata/eval/rotate/expected.json @@ -12,7 +12,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -48,7 +48,7 @@ "argSchema": { "properties": { "inputs": true, - "rotator": { + "provider": { "type": "string" }, "state": { @@ -70,7 +70,7 @@ "type": "object", "required": [ "inputs", - "rotator", + "provider", "state" ] }, @@ -94,30 +94,30 @@ "begin": { "line": 5, "column": 15, - "byte": 66 + "byte": 67 }, "end": { "line": 5, "column": 15, - "byte": 66 + "byte": 67 } }, "schema": { "type": "object" } }, - "rotator": { + "provider": { "range": { "environment": "rotate", "begin": { "line": 4, - "column": 16, - "byte": 47 + "column": 17, + "byte": 48 }, "end": { "line": 4, - "column": 20, - "byte": 51 + "column": 21, + "byte": 52 } }, "schema": { @@ -132,12 +132,12 @@ "begin": { "line": 7, "column": 9, - "byte": 90 + "byte": 91 }, "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -163,12 +163,12 @@ "begin": { "line": 7, "column": 9, - "byte": 90 + "byte": 91 }, "end": { "line": 7, "column": 10, - "byte": 91 + "byte": 92 } }, "b": { @@ -176,12 +176,12 @@ "begin": { "line": 8, "column": 9, - "byte": 106 + "byte": 107 }, "end": { "line": 8, "column": 10, - "byte": 107 + "byte": 108 } } }, @@ -192,12 +192,12 @@ "begin": { "line": 7, "column": 12, - "byte": 93 + "byte": 94 }, "end": { "line": 7, "column": 16, - "byte": 97 + "byte": 98 } }, "schema": { @@ -212,12 +212,12 @@ "begin": { "line": 8, "column": 12, - "byte": 109 + "byte": 110 }, "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -318,7 +318,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } }, @@ -376,12 +376,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } }, @@ -426,12 +426,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -456,12 +456,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 10, "column": 21, - "byte": 143 + "byte": 144 } }, "argSchema": { @@ -509,12 +509,12 @@ "begin": { "line": 11, "column": 15, - "byte": 159 + "byte": 160 }, "end": { "line": 11, "column": 15, - "byte": 159 + "byte": 160 } }, "schema": { @@ -527,12 +527,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 184 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -558,12 +558,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 184 }, "end": { "line": 13, "column": 10, - "byte": 184 + "byte": 185 } }, "b": { @@ -571,12 +571,12 @@ "begin": { "line": 14, "column": 9, - "byte": 199 + "byte": 200 }, "end": { "line": 14, "column": 10, - "byte": 200 + "byte": 201 } } }, @@ -587,12 +587,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 187 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 191 } }, "schema": { @@ -607,12 +607,12 @@ "begin": { "line": 14, "column": 12, - "byte": 202 + "byte": 203 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -642,7 +642,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -693,12 +693,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -964,7 +964,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -1002,7 +1002,7 @@ "argSchema": { "properties": { "inputs": true, - "rotator": { + "provider": { "type": "string" }, "state": { @@ -1024,7 +1024,7 @@ "type": "object", "required": [ "inputs", - "rotator", + "provider", "state" ] }, @@ -1048,30 +1048,30 @@ "begin": { "line": 5, "column": 15, - "byte": 66 + "byte": 67 }, "end": { "line": 5, "column": 15, - "byte": 66 + "byte": 67 } }, "schema": { "type": "object" } }, - "rotator": { + "provider": { "range": { "environment": "rotate", "begin": { "line": 4, - "column": 16, - "byte": 47 + "column": 17, + "byte": 48 }, "end": { "line": 4, - "column": 20, - "byte": 51 + "column": 21, + "byte": 52 } }, "schema": { @@ -1086,12 +1086,12 @@ "begin": { "line": 7, "column": 9, - "byte": 90 + "byte": 91 }, "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -1117,12 +1117,12 @@ "begin": { "line": 7, "column": 9, - "byte": 90 + "byte": 91 }, "end": { "line": 7, "column": 10, - "byte": 91 + "byte": 92 } }, "b": { @@ -1130,12 +1130,12 @@ "begin": { "line": 8, "column": 9, - "byte": 106 + "byte": 107 }, "end": { "line": 8, "column": 10, - "byte": 107 + "byte": 108 } } }, @@ -1146,12 +1146,12 @@ "begin": { "line": 7, "column": 12, - "byte": 93 + "byte": 94 }, "end": { "line": 7, "column": 16, - "byte": 97 + "byte": 98 } }, "schema": { @@ -1166,12 +1166,12 @@ "begin": { "line": 8, "column": 12, - "byte": 109 + "byte": 110 }, "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -1272,7 +1272,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } }, @@ -1301,7 +1301,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -1330,12 +1330,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } }, @@ -1359,12 +1359,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -1380,12 +1380,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -1412,12 +1412,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 10, "column": 21, - "byte": 143 + "byte": 144 } }, "argSchema": { @@ -1465,12 +1465,12 @@ "begin": { "line": 11, "column": 15, - "byte": 159 + "byte": 160 }, "end": { "line": 11, "column": 15, - "byte": 159 + "byte": 160 } }, "schema": { @@ -1483,12 +1483,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 184 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -1514,12 +1514,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 184 }, "end": { "line": 13, "column": 10, - "byte": 184 + "byte": 185 } }, "b": { @@ -1527,12 +1527,12 @@ "begin": { "line": 14, "column": 9, - "byte": 199 + "byte": 200 }, "end": { "line": 14, "column": 10, - "byte": 200 + "byte": 201 } } }, @@ -1543,12 +1543,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 187 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 191 } }, "schema": { @@ -1563,12 +1563,12 @@ "begin": { "line": 14, "column": 12, - "byte": 202 + "byte": 203 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -1600,7 +1600,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -1618,7 +1618,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -1635,7 +1635,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -1687,12 +1687,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -1705,12 +1705,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -1722,12 +1722,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -2016,7 +2016,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -2054,7 +2054,7 @@ "argSchema": { "properties": { "inputs": true, - "rotator": { + "provider": { "type": "string" }, "state": { @@ -2076,7 +2076,7 @@ "type": "object", "required": [ "inputs", - "rotator", + "provider", "state" ] }, @@ -2100,30 +2100,30 @@ "begin": { "line": 5, "column": 15, - "byte": 66 + "byte": 67 }, "end": { "line": 5, "column": 15, - "byte": 66 + "byte": 67 } }, "schema": { "type": "object" } }, - "rotator": { + "provider": { "range": { "environment": "rotate", "begin": { "line": 4, - "column": 16, - "byte": 47 + "column": 17, + "byte": 48 }, "end": { "line": 4, - "column": 20, - "byte": 51 + "column": 21, + "byte": 52 } }, "schema": { @@ -2138,12 +2138,12 @@ "begin": { "line": 7, "column": 9, - "byte": 90 + "byte": 91 }, "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -2169,12 +2169,12 @@ "begin": { "line": 7, "column": 9, - "byte": 90 + "byte": 91 }, "end": { "line": 7, "column": 10, - "byte": 91 + "byte": 92 } }, "b": { @@ -2182,12 +2182,12 @@ "begin": { "line": 8, "column": 9, - "byte": 106 + "byte": 107 }, "end": { "line": 8, "column": 10, - "byte": 107 + "byte": 108 } } }, @@ -2198,12 +2198,12 @@ "begin": { "line": 7, "column": 12, - "byte": 93 + "byte": 94 }, "end": { "line": 7, "column": 16, - "byte": 97 + "byte": 98 } }, "schema": { @@ -2218,12 +2218,12 @@ "begin": { "line": 8, "column": 12, - "byte": 109 + "byte": 110 }, "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } }, "schema": { @@ -2324,7 +2324,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } }, @@ -2353,7 +2353,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -2382,12 +2382,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } }, @@ -2411,12 +2411,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -2432,12 +2432,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -2464,12 +2464,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 10, "column": 21, - "byte": 143 + "byte": 144 } }, "argSchema": { @@ -2517,12 +2517,12 @@ "begin": { "line": 11, "column": 15, - "byte": 159 + "byte": 160 }, "end": { "line": 11, "column": 15, - "byte": 159 + "byte": 160 } }, "schema": { @@ -2535,12 +2535,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 184 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -2566,12 +2566,12 @@ "begin": { "line": 13, "column": 9, - "byte": 183 + "byte": 184 }, "end": { "line": 13, "column": 10, - "byte": 184 + "byte": 185 } }, "b": { @@ -2579,12 +2579,12 @@ "begin": { "line": 14, "column": 9, - "byte": 199 + "byte": 200 }, "end": { "line": 14, "column": 10, - "byte": 200 + "byte": 201 } } }, @@ -2595,12 +2595,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 187 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 191 } }, "schema": { @@ -2615,12 +2615,12 @@ "begin": { "line": 14, "column": 12, - "byte": 202 + "byte": 203 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } }, "schema": { @@ -2652,7 +2652,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -2670,7 +2670,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -2687,7 +2687,7 @@ "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -2739,12 +2739,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -2757,12 +2757,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -2774,12 +2774,12 @@ "begin": { "line": 10, "column": 5, - "byte": 127 + "byte": 128 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -3044,7 +3044,7 @@ }, "rotatePatches": [ { - "DocPath": "full.fn::rotate.state", + "DocPath": "full[\"fn::rotate\"].state", "Replacement": { "value": { "a": { @@ -3055,12 +3055,12 @@ "begin": { "line": 8, "column": 12, - "byte": 109 + "byte": 110 }, "end": { "line": 8, "column": 16, - "byte": 113 + "byte": 114 } } } @@ -3073,12 +3073,12 @@ "begin": { "line": 7, "column": 12, - "byte": 93 + "byte": 94 }, "end": { "line": 7, "column": 16, - "byte": 97 + "byte": 98 } } } @@ -3101,7 +3101,7 @@ } }, { - "DocPath": "short.fn::rotate::swap.state", + "DocPath": "short[\"fn::rotate::swap\"].state", "Replacement": { "value": { "a": { @@ -3112,12 +3112,12 @@ "begin": { "line": 14, "column": 12, - "byte": 202 + "byte": 203 }, "end": { "line": 14, "column": 16, - "byte": 206 + "byte": 207 } } } @@ -3130,12 +3130,12 @@ "begin": { "line": 13, "column": 12, - "byte": 186 + "byte": 187 }, "end": { "line": 13, "column": 16, - "byte": 190 + "byte": 191 } } } From d5f036c885ceea7bdef006f777be42c37a2aedb0 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:06:41 -0800 Subject: [PATCH 30/30] clarify docstring --- provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider.go b/provider.go index 4e0904c5..7caf062d 100644 --- a/provider.go +++ b/provider.go @@ -31,7 +31,7 @@ type Provider interface { } // A Rotator enables environments to rotate a secret. -// Rotators persist internal state by writing back to the environment. +// It is the responsibility of the caller to appropriately persist rotation state (e.g. by writing it back to the environment definition). type Rotator interface { // Schema returns the rotator's input, state, and output schemata. Schema() (inputs, state, outputs *schema.Schema)