Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/yummy-buckets-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink-deployments-framework": patch
---

fix: populate Config attribute in hook params
32 changes: 19 additions & 13 deletions engine/cld/changeset/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ type Configurations struct {
type internalChangeSet interface {
noop() // unexported function to prevent arbitrary structs from implementing ChangeSet.
Apply(env fdeployment.Environment) (fdeployment.ChangesetOutput, error)
applyWithInput(env fdeployment.Environment, inputStr string) (fdeployment.ChangesetOutput, error)
Configurations() (Configurations, error)
applyWithInput(env fdeployment.Environment, input any) (fdeployment.ChangesetOutput, error)
resolvedInput(input string) (any, error)
}
Comment thread
gustavogama-cll marked this conversation as resolved.

type ChangeSet internalChangeSet
Expand Down Expand Up @@ -294,23 +295,17 @@ func (ccs ChangeSetImpl[C]) Apply(env fdeployment.Environment) (fdeployment.Chan
return ccs.changeset.operation.Apply(env, c)
}

func (ccs ChangeSetImpl[C]) applyWithInput(env fdeployment.Environment, inputStr string) (fdeployment.ChangesetOutput, error) {
if inputStr == "" {
return ccs.Apply(env)
}
if ccs.configProviderWithInput == nil {
return ccs.Apply(env)
func (ccs ChangeSetImpl[C]) applyWithInput(env fdeployment.Environment, input any) (fdeployment.ChangesetOutput, error) {
cInput, ok := input.(C)
if !ok {
return fdeployment.ChangesetOutput{}, fmt.Errorf("invalid input type: expected %T but got %T", *new(C), input)
Comment thread
gustavogama-cll marked this conversation as resolved.
}
Comment thread
gustavogama-cll marked this conversation as resolved.

c, err := ccs.configProviderWithInput(inputStr)
if err != nil {
return fdeployment.ChangesetOutput{}, err
}
if err := ccs.changeset.operation.VerifyPreconditions(env, c); err != nil {
if err := ccs.changeset.operation.VerifyPreconditions(env, cInput); err != nil {
return fdeployment.ChangesetOutput{}, err
}

return ccs.changeset.operation.Apply(env, c)
return ccs.changeset.operation.Apply(env, cInput)
}

func (ccs ChangeSetImpl[C]) Configurations() (Configurations, error) {
Expand All @@ -335,6 +330,17 @@ func (ccs ChangeSetImpl[C]) Configurations() (Configurations, error) {
}, nil
}

func (ccs ChangeSetImpl[C]) resolvedInput(input string) (any, error) {
if input != "" && ccs.configProviderWithInput != nil {
return ccs.configProviderWithInput(input)
}
if ccs.configProvider != nil {
return ccs.configProvider()
}

return *new(C), errors.New("no configuration provider found")
}

// WithPreHooks appends pre-hooks to this changeset. Multiple calls are additive.
func (ccs ChangeSetImpl[C]) WithPreHooks(hooks ...PreHook) ConfiguredChangeSet {
ccs.preHooks = append(slices.Clone(ccs.preHooks), hooks...)
Expand Down
36 changes: 36 additions & 0 deletions engine/cld/changeset/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,42 @@ func TestWithJSON_UseNumberForAnyPayload(t *testing.T) {
require.NoError(t, err)
}

func TestChangeSetImpl_configuration(t *testing.T) { //nolint:paralleltest
configuredChangeset := Configure(MyChangeSet)
input := `{"payload":"config"}`

t.Run("With", func(t *testing.T) { //nolint:paralleltest
got, err := configuredChangeset.With("config").resolvedInput(input)
require.NoError(t, err)
require.Equal(t, "config", got)
})

t.Run("WithConfigFrom", func(t *testing.T) { //nolint:paralleltest
got, err := configuredChangeset.WithConfigFrom(func() (string, error) {
return "config", nil
}).resolvedInput(input)
require.NoError(t, err)
require.Equal(t, "config", got)
})

t.Run("WithEnvInput", func(t *testing.T) { //nolint:paralleltest
t.Setenv("DURABLE_PIPELINE_INPUT", input)
got, err := configuredChangeset.WithEnvInput().resolvedInput(input)
require.NoError(t, err)
require.Equal(t, "config", got)
})

t.Run("WithConfigResolver", func(t *testing.T) { //nolint:paralleltest
t.Setenv("DURABLE_PIPELINE_INPUT", input)
resolver := func(input string) (string, error) {
return "resolved " + input, nil
}
got, err := configuredChangeset.WithConfigResolver(resolver).resolvedInput(input)
require.NoError(t, err)
require.Equal(t, "resolved config", got)
})
}

func TestConfigurations_ConfigResolverInfo(t *testing.T) {
t.Parallel()

Expand Down
5 changes: 4 additions & 1 deletion engine/cld/changeset/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ type PostProposalHookParams struct {
Env ProposalHookEnv
ChangesetKey string
Proposal *mcms.TimelockProposal
Input any
Config any
Reports []MCMSTimelockExecuteReport

// Deprecated: use `Config` instead. Will be removed in a future version.
Comment thread
gustavogama-cll marked this conversation as resolved.
Input any
}

// PreHookFunc is the signature for functions that run before changeset Apply.
Expand Down
3 changes: 2 additions & 1 deletion engine/cld/changeset/mcms.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (*EVMForkContext) ChainFamily() string {
// 1. Per-changeset post-proposal-hooks
// 2. Global post-proposal-hooks
func (r *ChangesetsRegistry) RunProposalHooks(
key string, e fdeployment.Environment, proposal *mcms.TimelockProposal, input any,
key string, e fdeployment.Environment, proposal *mcms.TimelockProposal, input, config any,
reports []MCMSTimelockExecuteReport, forkCtx ForkContext,
) error {
applySnapshot, err := r.getApplySnapshot(key)
Expand All @@ -106,6 +106,7 @@ func (r *ChangesetsRegistry) RunProposalHooks(
ChangesetKey: key,
Proposal: proposal,
Input: input,
Config: config,
Reports: reports,
}

Expand Down
6 changes: 4 additions & 2 deletions engine/cld/changeset/mcms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func Test_RunProposalHooks(t *testing.T) {
execLogs := []string{}
registry := tt.setup(&execLogs)

err := registry.RunProposalHooks(tt.key, hookTestEnv(t), &mcms.TimelockProposal{}, nil, nil, nil)
err := registry.RunProposalHooks(tt.key, hookTestEnv(t), &mcms.TimelockProposal{}, nil, nil, nil, nil)

if tt.wantErr == "" {
require.NoError(t, err)
Expand All @@ -165,6 +165,7 @@ func Test_RunProposalHooks_HookReceivesCorrectParams(t *testing.T) {

proposal := &mcms.TimelockProposal{}
input := "test-input"
config := "test-config"
reports := []MCMSTimelockExecuteReport{{Type: MCMSTimelockExecuteReportType}}

var receivedParams PostProposalHookParams
Expand All @@ -181,14 +182,15 @@ func Test_RunProposalHooks_HookReceivesCorrectParams(t *testing.T) {
}},
}

err := r.RunProposalHooks("test-cs", hookTestEnv(t), proposal, input, reports, nil)
err := r.RunProposalHooks("test-cs", hookTestEnv(t), proposal, input, config, reports, nil)
require.NoError(t, err)

expectedParams := PostProposalHookParams{
Env: ProposalHookEnv{Name: "test-env"},
ChangesetKey: "test-cs",
Proposal: proposal,
Input: input,
Config: config,
Reports: reports,
}
require.Empty(t, cmp.Diff(expectedParams, receivedParams,
Expand Down
8 changes: 6 additions & 2 deletions engine/cld/changeset/postprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ func (ccs PostProcessingChangeSetImpl[C]) Apply(env fdeployment.Environment) (fd
}

func (ccs PostProcessingChangeSetImpl[C]) applyWithInput(
env fdeployment.Environment, inputStr string,
env fdeployment.Environment, input any,
) (fdeployment.ChangesetOutput, error) {
env.Logger.Debugf("Post-processing ChangesetOutput from %T", ccs.changeset.changeset.operation)
output, err := ccs.changeset.applyWithInput(env, inputStr)
output, err := ccs.changeset.applyWithInput(env, input)
if err != nil {
return output, err
}
Expand Down Expand Up @@ -70,6 +70,10 @@ func (ccs PostProcessingChangeSetImpl[C]) WithPostProposalHooks(hooks ...PostPro
return ccs
}

func (ccs PostProcessingChangeSetImpl[C]) resolvedInput(input string) (any, error) {
return ccs.changeset.resolvedInput(input)
}

func (ccs PostProcessingChangeSetImpl[C]) getPreHooks() []PreHook { return ccs.preHooks }
func (ccs PostProcessingChangeSetImpl[C]) getPostHooks() []PostHook { return ccs.postHooks }
func (ccs PostProcessingChangeSetImpl[C]) getPostProposalHooks() []PostProposalHook {
Expand Down
14 changes: 14 additions & 0 deletions engine/cld/changeset/postprocess_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,17 @@ func TestChangesets_PostProcess(t *testing.T) {
require.NoError(t, err)
assert.Nil(t, configs.InputChainOverrides)
}

func TestChangesets_PostProcess_configuration(t *testing.T) {
t.Parallel()

noopPostProcessor := func(_ fdeployment.Environment, o fdeployment.ChangesetOutput) (fdeployment.ChangesetOutput, error) {
return o, nil
}
input := `{"payload":"config"}`

got, err := Configure(MyChangeSet).With("config").ThenWith(noopPostProcessor).resolvedInput(input)

require.NoError(t, err)
require.Equal(t, "config", got)
}
106 changes: 61 additions & 45 deletions engine/cld/changeset/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,90 +203,92 @@ func (r *ChangesetsRegistry) Apply(
opt(&cfg)
}

if !cfg.runHooks {
entry, err := r.getApplyEntry(key)
applySnapshot, err := r.getApplySnapshot(key)
if err != nil {
return fdeployment.ChangesetOutput{}, err
}

resolvedInput, err := applySnapshot.registryEntry.changeset.resolvedInput(cfg.inputStr)
if err != nil {
return fdeployment.ChangesetOutput{}, fmt.Errorf("failed to get changeset configuration: %w", err)
}

if cfg.runHooks {
err = runPreHooks(e, key, resolvedInput, applySnapshot)
if err != nil {
return fdeployment.ChangesetOutput{}, err
}

return entry.changeset.applyWithInput(e, cfg.inputStr)
}

applySnapshot, err := r.getApplySnapshot(key)
if err != nil {
return fdeployment.ChangesetOutput{}, err
}
output, applyErr := applySnapshot.registryEntry.changeset.applyWithInput(e, resolvedInput)

hookEnv := HookEnv{
Name: e.Name,
Logger: e.Logger,
if cfg.runHooks {
err = runPostHooks(e, key, resolvedInput, output, applyErr, applySnapshot)
if err != nil {
return fdeployment.ChangesetOutput{}, err
}
}

return output, applyErr
}

func runPreHooks(e fdeployment.Environment, key string, resolvedInput any, applySnapshot applySnapshot) error {
preParams := PreHookParams{
Env: hookEnv,
Env: HookEnv{Name: e.Name, Logger: e.Logger},
ChangesetKey: key,
Config: resolvedInput,
}

for _, h := range applySnapshot.globalPreHooks {
if err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error {
return h.Func(ctx, preParams)
}); err != nil {
return fdeployment.ChangesetOutput{}, fmt.Errorf("global pre-hook %q failed: %w", h.Name, err)
err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error { return h.Func(ctx, preParams) })
if err != nil {
return fmt.Errorf("global pre-hook %q failed: %w", h.Name, err)
}
}

for _, h := range applySnapshot.registryEntry.preHooks {
if err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error {
return h.Func(ctx, preParams)
}); err != nil {
return fdeployment.ChangesetOutput{}, fmt.Errorf("pre-hook %q failed: %w", h.Name, err)
err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error { return h.Func(ctx, preParams) })
if err != nil {
return fmt.Errorf("changeset pre-hook %q failed: %w", h.Name, err)
}
}

output, applyErr := applySnapshot.registryEntry.changeset.applyWithInput(e, cfg.inputStr)
return nil
}

func runPostHooks(
e fdeployment.Environment, key string, resolvedInput any, output fdeployment.ChangesetOutput,
applyErr error, applySnapshot applySnapshot,
) error {
postParams := PostHookParams{
Env: hookEnv,
Env: HookEnv{Name: e.Name, Logger: e.Logger},
ChangesetKey: key,
Config: resolvedInput,
Output: output,
Err: applyErr,
}

for _, h := range applySnapshot.registryEntry.postHooks {
if err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error {
return h.Func(ctx, postParams)
}); err != nil {
err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error { return h.Func(ctx, postParams) })
if err != nil {
if applyErr != nil {
e.Logger.Warnw("post-hook failed after changeset error",
"hook", h.Name, "hookErr", err, "changesetErr", applyErr)
e.Logger.Warnw("post-hook failed after changeset error", "hook", h.Name, "hookErr", err, "changesetErr", applyErr)
} else {
return output, fmt.Errorf("post-hook %q failed: %w", h.Name, err)
return fmt.Errorf("changeset post-hook %q failed: %w", h.Name, err)
}
}
}

for _, h := range applySnapshot.globalPostHooks {
if err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error {
return h.Func(ctx, postParams)
}); err != nil {
err := ExecuteHook(e, h.HookDefinition, func(ctx context.Context) error { return h.Func(ctx, postParams) })
if err != nil {
if applyErr != nil {
e.Logger.Warnw("global post-hook failed after changeset error",
"hook", h.Name, "hookErr", err, "changesetErr", applyErr)
e.Logger.Warnw("global post-hook failed after changeset error", "hook", h.Name, "hookErr", err, "changesetErr", applyErr)
} else {
return output, fmt.Errorf("global post-hook %q failed: %w", h.Name, err)
return fmt.Errorf("global post-hook %q failed: %w", h.Name, err)
}
}
}

return output, applyErr
}

// getApplyEntry reads and validates a changeset entry under the mutex.
func (r *ChangesetsRegistry) getApplyEntry(key string) (registryEntry, error) {
r.mu.Lock()
defer r.mu.Unlock()

return r.getApplyEntryLocked(key)
return nil
}

type applySnapshot struct {
Expand Down Expand Up @@ -339,6 +341,20 @@ func (r *ChangesetsRegistry) GetChangesetOptions(key string) (ChangesetConfig, e
return entry.options, nil
}

// GetResolvedInput retrieves the configuration for a changeset.
// \"input\" is the optional input string passed to \"registry.Apply()\".
func (r *ChangesetsRegistry) GetResolvedInput(key string, input string) (any, error) {
entry, ok := r.entries[key]
if !ok {
return nil, fmt.Errorf("changeset '%s' not found", key)
}
if entry.IsArchived() {
return nil, fmt.Errorf("changeset '%s' is archived at SHA '%s'", key, *entry.gitSHA)
}

Comment thread
gustavogama-cll marked this conversation as resolved.
return entry.changeset.resolvedInput(input)
}

// GetConfigurations retrieves the configurations for a changeset.
func (r *ChangesetsRegistry) GetConfigurations(key string) (Configurations, error) {
entry, ok := r.entries[key]
Expand Down
Loading
Loading