diff --git a/internal/directives/output_composer.go b/internal/directives/output_composer.go new file mode 100644 index 0000000000..3ece6bcdc5 --- /dev/null +++ b/internal/directives/output_composer.go @@ -0,0 +1,70 @@ +package directives + +import ( + "context" + "fmt" + "maps" + + "github.com/xeipuuv/gojsonschema" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" +) + +func init() { + builtins.RegisterPromotionStepRunner(newOutputComposer(), nil) +} + +// outputComposer is an implementation of the PromotionStepRunner interface +// that allows composing outputs from previous steps into new outputs. +// +// It works based on the PromotionStepContext.Config field allowing to an +// arbitrary number of key-value pairs to be exported as outputs. +// Because the values are allowed to be expressions and can contain +// references to outputs from previous steps, this allows for remapping +// the outputs of previous steps to new keys, or even combining them +// into new structures. +type outputComposer struct { + schemaLoader gojsonschema.JSONLoader +} + +// newOutputComposer returns an implementation of the PromotionStepRunner +// interface that composes output from previous steps into new output. +func newOutputComposer() PromotionStepRunner { + r := &outputComposer{} + r.schemaLoader = getConfigSchemaLoader(r.Name()) + return r +} + +// Name implements the PromotionStepRunner interface. +func (c *outputComposer) Name() string { + return "compose-output" +} + +// RunPromotionStep implements the PromotionStepRunner interface. +func (c *outputComposer) RunPromotionStep( + _ context.Context, + stepCtx *PromotionStepContext, +) (PromotionStepResult, error) { + // Validate the configuration against the JSON Schema. + if err := validate(c.schemaLoader, gojsonschema.NewGoLoader(stepCtx.Config), c.Name()); err != nil { + return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored}, err + } + + // Convert the configuration into a typed object. + cfg, err := ConfigToStruct[ComposeOutput](stepCtx.Config) + if err != nil { + return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored}, + fmt.Errorf("could not convert config into %s config: %w", c.Name(), err) + } + + return c.runPromotionStep(cfg) +} + +func (c *outputComposer) runPromotionStep( + cfg ComposeOutput, +) (PromotionStepResult, error) { + return PromotionStepResult{ + Status: kargoapi.PromotionPhaseSucceeded, + Output: maps.Clone(cfg), + }, nil +} diff --git a/internal/directives/schemas/compose-output.json b/internal/directives/schemas/compose-output.json new file mode 100644 index 0000000000..76421df1df --- /dev/null +++ b/internal/directives/schemas/compose-output.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "ComposeOutput", + "type": "object", + "minProperties": 1 +} diff --git a/internal/directives/zz_config_types.go b/internal/directives/zz_config_types.go index e09b38c60d..c1d89150b7 100644 --- a/internal/directives/zz_config_types.go +++ b/internal/directives/zz_config_types.go @@ -4,6 +4,8 @@ package directives type CommonDefs interface{} +type ComposeOutput map[string]interface{} + type ArgoCDUpdateConfig struct { Apps []ArgoCDAppUpdate `json:"apps"` FromOrigin *AppFromOrigin `json:"fromOrigin,omitempty"` diff --git a/ui/src/gen/directives/compose-output.json b/ui/src/gen/directives/compose-output.json new file mode 100644 index 0000000000..b168b74f6a --- /dev/null +++ b/ui/src/gen/directives/compose-output.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "ComposeOutput", + "type": "object", + "minProperties": 1 +} \ No newline at end of file