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
66 changes: 66 additions & 0 deletions cre/changesets/workflow_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package changesets

import (
"errors"
"fmt"
"strings"

creops "github.com/smartcontractkit/cld-changesets/cre/operations"

cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
cfgenv "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/env"
fwops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
)

// CREWorkflowDeleteChangeset writes workflow.yaml from registry/name, resolves project.yaml, and runs `cre workflow delete`.
type CREWorkflowDeleteChangeset struct{}

// VerifyPreconditions ensures the environment can run CRE and input is valid.
func (CREWorkflowDeleteChangeset) VerifyPreconditions(e cldf.Environment, input creops.CREWorkflowDeleteInput) error {
if e.CRERunner == nil {
return errors.New("cre runner is not available in this environment")
}
if e.CRERunner.CLI() == nil {
return errors.New("cre CLI runner is not configured")
}
if err := input.Validate(); err != nil {
return err
}
if strings.TrimSpace(input.DeploymentRegistry) == "" {
return errors.New("deploymentRegistry is required")
}
if strings.TrimSpace(input.DonFamily) == "" {
return errors.New("donFamily is required")
}

return nil
}

// Apply loads CRE config and runs the CRE workflow delete operation.
func (CREWorkflowDeleteChangeset) Apply(e cldf.Environment, input creops.CREWorkflowDeleteInput) (cldf.ChangesetOutput, error) {
envCfg, err := cfgenv.LoadEnv()
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("load CRE env config: %w", err)
}

deps := creops.CREDeployDeps{
CLI: e.CRERunner.CLI(),
CRECfg: envCfg.CRE,
EVMDeployerKey: envCfg.Onchain.EVM.DeployerKey,
}

report, err := fwops.ExecuteOperation[creops.CREWorkflowDeleteInput, creops.CREWorkflowDeleteOutput, creops.CREDeployDeps](
e.OperationsBundle,
creops.CREWorkflowDeleteOp,
deps,
input,
)
out := cldf.ChangesetOutput{
Reports: []fwops.Report[any, any]{report.ToGenericReport()},
}
if err != nil {
return out, err
}

return out, nil
}
194 changes: 194 additions & 0 deletions cre/changesets/workflow_delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package changesets

import (
"errors"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/cld-changesets/cre/operations"

fcre "github.com/smartcontractkit/chainlink-deployments-framework/cre"
creartifacts "github.com/smartcontractkit/chainlink-deployments-framework/cre/artifacts"
cremocks "github.com/smartcontractkit/chainlink-deployments-framework/cre/mocks"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
testenv "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment"
)

func validDeleteInput(t *testing.T) operations.CREWorkflowDeleteInput {
t.Helper()
projectPath := filepath.Join(t.TempDir(), "project.yaml")
require.NoError(t, os.WriteFile(projectPath, []byte("cld-deploy:\n cre-cli:\n don-family: zone\n"), 0o600))

return operations.CREWorkflowDeleteInput{
WorkflowName: "wf",
DonFamily: "zone",
DeploymentRegistry: "private",
Project: creartifacts.NewConfigSourceLocal(projectPath),
}
}

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

mockCLI := cremocks.NewMockCLIRunner(t)
envNoCLI := newTestEnv(t, testenv.WithCRERunner(fcre.NewRunner()))
envWithCLI := newTestEnv(t, testenv.WithCRERunner(fcre.NewRunner(fcre.WithCLI(mockCLI))))
envNoCRE := newTestEnv(t)

good := validDeleteInput(t)

tests := []struct {
name string
env cldf.Environment
input func() operations.CREWorkflowDeleteInput
wantErr string
}{
{
name: "no CRERunner",
env: *envNoCRE,
wantErr: "cre runner is not available in this environment",
},
{
name: "CRERunner without CLI",
env: *envNoCLI,
wantErr: "CLI runner is not configured",
},
{
name: "missing project",
env: *envWithCLI,
input: func() operations.CREWorkflowDeleteInput {
in := good
in.Project = creartifacts.ConfigSource{}

return in
},
wantErr: "project:",
},
{
name: "missing deploymentRegistry",
env: *envWithCLI,
input: func() operations.CREWorkflowDeleteInput {
in := good
in.DeploymentRegistry = ""

return in
},
wantErr: "deploymentRegistry is required",
},
{
name: "missing donFamily",
env: *envWithCLI,
input: func() operations.CREWorkflowDeleteInput {
in := good
in.DonFamily = ""

return in
},
wantErr: "donFamily is required",
},
{
name: "missing workflowName",
env: *envWithCLI,
input: func() operations.CREWorkflowDeleteInput {
in := good
in.WorkflowName = ""

return in
},
wantErr: "workflowName",
},
{
name: "valid input passes",
env: *envWithCLI,
},
}

cs := CREWorkflowDeleteChangeset{}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
input := good
if tc.input != nil {
input = tc.input()
}
err := cs.VerifyPreconditions(tc.env, input)
if tc.wantErr == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, tc.wantErr)
}
})
}
}

func TestCREWorkflowDeleteChangeset_Apply(t *testing.T) {
t.Setenv("ONCHAIN_EVM_DEPLOYER_KEY", "abc123")

cs := CREWorkflowDeleteChangeset{}
input := validDeleteInput(t)

t.Run("success returns report", func(t *testing.T) { //nolint:paralleltest
mockCLI := cremocks.NewMockCLIRunner(t)
mockCLI.EXPECT().ContextRegistries().Return([]fcre.ContextRegistryEntry{
{ID: "private", Type: "off-chain"},
}).Once()
mockCLI.EXPECT().
Run(mock.Anything, (map[string]string)(nil), matchCLIArgs("workflow", "delete")).
Return(&fcre.CallResult{
ExitCode: 0,
Stdout: []byte("ok"),
}, nil).
Once()
env := newTestEnv(t, testenv.WithCRERunner(fcre.NewRunner(fcre.WithCLI(mockCLI))))

out, err := cs.Apply(*env, input)
require.NoError(t, err)
require.Len(t, out.Reports, 1)
output, ok := out.Reports[0].Output.(operations.CREWorkflowDeleteOutput)
require.True(t, ok)
require.Equal(t, 0, output.ExitCode)
require.Equal(t, "ok", output.Stdout)
})

t.Run("operation error returns report and error", func(t *testing.T) { //nolint:paralleltest
mockCLI := cremocks.NewMockCLIRunner(t)
mockCLI.EXPECT().ContextRegistries().Return([]fcre.ContextRegistryEntry{
{ID: "private", Type: "off-chain"},
}).Once()
mockCLI.EXPECT().Run(mock.Anything, mock.Anything, mock.Anything).
Return((*fcre.CallResult)(nil), errors.New("op failed")).
Once()
env := newTestEnv(t, testenv.WithCRERunner(fcre.NewRunner(fcre.WithCLI(mockCLI))))

out, err := cs.Apply(*env, input)
require.ErrorContains(t, err, "cre workflow delete: op failed")
require.Len(t, out.Reports, 1)
output, ok := out.Reports[0].Output.(operations.CREWorkflowDeleteOutput)
require.True(t, ok)
require.Empty(t, output.Stdout)
})

t.Run("on-chain registry injects deployer key env", func(t *testing.T) { //nolint:paralleltest
mockCLI := cremocks.NewMockCLIRunner(t)
mockCLI.EXPECT().ContextRegistries().Return([]fcre.ContextRegistryEntry{
{ID: "onchain-reg", Type: "on-chain"},
}).Once()
mockCLI.EXPECT().
Run(mock.Anything, mock.MatchedBy(func(env map[string]string) bool {
return env != nil && env["CRE_ETH_PRIVATE_KEY"] == "abc123"
}), mock.Anything).
Return(&fcre.CallResult{ExitCode: 0}, nil).
Once()
env := newTestEnv(t, testenv.WithCRERunner(fcre.NewRunner(fcre.WithCLI(mockCLI))))

onChainInput := input
onChainInput.DeploymentRegistry = "onchain-reg"
out, err := cs.Apply(*env, onChainInput)
require.NoError(t, err)
require.Len(t, out.Reports, 1)
})
}
Loading
Loading