Skip to content
Draft
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
22 changes: 22 additions & 0 deletions cre/changesets/workflow_deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,28 @@ func TestCREWorkflowDeployChangeset_Apply(t *testing.T) {
require.NoError(t, err)
require.Len(t, out.Reports, 1)
})

t.Run("APIKeyName propagates through changeset to selected CLI", func(t *testing.T) { //nolint:paralleltest
inner := cremocks.NewMockCLIRunner(t)
inner.EXPECT().ContextRegistries().Return([]fcre.ContextRegistryEntry{
{ID: "private", Type: "off-chain"},
}).Once()
inner.EXPECT().
Run(mock.Anything, (map[string]string)(nil), matchCLIArgs("workflow", "deploy")).
Return(&fcre.CallResult{ExitCode: 0, Stdout: []byte("ok")}, nil).
Once()

outer := cremocks.NewMockCLIRunner(t)
outer.EXPECT().WithNamedAPIKey("prod-1").Return(inner, nil).Once()

env := newTestEnv(t, testenv.WithCRERunner(fcre.NewRunner(fcre.WithCLI(outer))))

namedInput := input
namedInput.APIKeyName = "prod-1"
out, err := cs.Apply(*env, namedInput)
require.NoError(t, err)
require.Len(t, out.Reports, 1)
})
}

func matchCLIArgs(wantArgs ...string) any {
Expand Down
19 changes: 16 additions & 3 deletions cre/operations/workflow_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
// Optional - TargetName is the CRE CLI target key that must match a top-level key
// in project.yaml. Defaults to CREDeployTargetName ("cld-deploy") when empty.
TargetName string `json:"targetName,omitempty" yaml:"targetName,omitempty"`
// Optional - APIKeyName selects which CRE API key to use when the runner is
// configured with multiple named keys (e.g. CRE_API_KEY={"prod":"...","stg":"..."}).
// Leave empty when the runner is configured with a single key.
APIKeyName string `json:"apiKeyName,omitempty" yaml:"apiKeyName,omitempty"`
}

// resolveTargetName returns the user-specified target name, falling back to [CREDeployTargetName].
Expand All @@ -74,14 +78,23 @@
// CREWorkflowDeployOp deploys a workflow via the CRE CLI (single side effect: CLI invocation).
var CREWorkflowDeployOp = fwops.NewOperation(
"cre-workflow-deploy",
semver.MustParse("1.0.0"),
semver.MustParse("1.1.0"),
"Deploys a CRE workflow via the CRE CLI subprocess",
func(b fwops.Bundle, deps CREDeployDeps, input CREWorkflowDeployInput) (CREWorkflowDeployOutput, error) {
ctx := b.GetContext()
if deps.CLI == nil {
return CREWorkflowDeployOutput{}, errors.New("cre CLIRunner is nil")
}

cli := deps.CLI
if input.APIKeyName != "" {
selected, err := deps.CLI.WithNamedAPIKey(input.APIKeyName)

Check failure on line 91 in cre/operations/workflow_deploy.go

View workflow job for this annotation

GitHub Actions / Tests

deps.CLI.WithNamedAPIKey undefined (type cre.CLIRunner has no field or method WithNamedAPIKey)
if err != nil {
return CREWorkflowDeployOutput{}, fmt.Errorf("select cre api key %q: %w", input.APIKeyName, err)
}
cli = selected
}

workDir, err := os.MkdirTemp("", "cre-workflow-artifacts-*")
if err != nil {
return CREWorkflowDeployOutput{}, fmt.Errorf("mkdir temp workflow artifacts: %w", err)
Expand Down Expand Up @@ -136,7 +149,7 @@
return CREWorkflowDeployOutput{}, fmt.Errorf("write workflow.yaml: %w", err)
}

ctxCfg, err := crecli.BuildContextConfig(input.DonFamily, input.Context, deps.CRECfg, deps.CLI.ContextRegistries())
ctxCfg, err := crecli.BuildContextConfig(input.DonFamily, input.Context, deps.CRECfg, cli.ContextRegistries())
if err != nil {
return CREWorkflowDeployOutput{}, err
}
Expand All @@ -163,7 +176,7 @@
"CRE_ETH_PRIVATE_KEY": deps.EVMDeployerKey,
}
}
res, runErr := deps.CLI.Run(ctx, runEnv, args...)
res, runErr := cli.Run(ctx, runEnv, args...)
if runErr != nil {
var exitErr *fcre.ExitError
if errors.As(runErr, &exitErr) {
Expand Down
66 changes: 66 additions & 0 deletions cre/operations/workflow_deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package operations

import (
"context"
"errors"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -165,6 +166,71 @@ func TestCREWorkflowDeployOp(t *testing.T) {
require.Equal(t, "err", out.Output.Stderr)
},
},
{
name: "APIKeyName selects cli before run",
input: func(t *testing.T) CREWorkflowDeployInput {
t.Helper()

return CREWorkflowDeployInput{
WorkflowBundle: creartifacts.WorkflowBundle{
WorkflowName: "wf",
Binary: creartifacts.NewBinarySourceLocal(writeFile(t, "x.wasm", []byte("wasm"))),
Config: creartifacts.NewConfigSourceLocal(writeFile(t, "cfg.json", []byte(`{}`))),
DonFamily: "feeds-zone-a",
DeploymentRegistry: "private",
},
Project: creartifacts.NewConfigSourceLocal(writeFile(t, "project.yaml", []byte("cld-deploy: {}\n"))),
APIKeyName: "prod-1",
}
},
setupCLI: func(t *testing.T) *cremocks.MockCLIRunner {
t.Helper()
inner := cremocks.NewMockCLIRunner(t)
inner.EXPECT().ContextRegistries().Return(testRegistries()).Once()
inner.EXPECT().Run(mock.Anything, mock.Anything, matchCLIArgs("workflow", "deploy")).Return(
&fcre.CallResult{ExitCode: 0, Stdout: []byte("ok")}, nil,
).Once()

outer := cremocks.NewMockCLIRunner(t)
outer.EXPECT().WithNamedAPIKey("prod-1").Return(inner, nil).Once()

return outer
},
assert: func(t *testing.T, _ fwops.Report[CREWorkflowDeployInput, CREWorkflowDeployOutput], err error) {
t.Helper()
require.NoError(t, err)
},
},
{
name: "unknown APIKeyName short-circuits before CLI work",
input: func(t *testing.T) CREWorkflowDeployInput {
t.Helper()

return CREWorkflowDeployInput{
WorkflowBundle: creartifacts.WorkflowBundle{
WorkflowName: "wf",
Binary: creartifacts.NewBinarySourceLocal(writeFile(t, "x.wasm", []byte("wasm"))),
Config: creartifacts.NewConfigSourceLocal(writeFile(t, "cfg.json", []byte(`{}`))),
DonFamily: "feeds-zone-a",
DeploymentRegistry: "private",
},
Project: creartifacts.NewConfigSourceLocal(writeFile(t, "project.yaml", []byte("cld-deploy: {}\n"))),
APIKeyName: "missing",
}
},
setupCLI: func(t *testing.T) *cremocks.MockCLIRunner {
t.Helper()
outer := cremocks.NewMockCLIRunner(t)
outer.EXPECT().WithNamedAPIKey("missing").Return(nil, errors.New(`API key "missing" not configured`)).Once()

return outer
},
assert: func(t *testing.T, _ fwops.Report[CREWorkflowDeployInput, CREWorkflowDeployOutput], err error) {
t.Helper()
require.ErrorContains(t, err, `select cre api key "missing"`)
require.ErrorContains(t, err, "not configured")
},
},
}

for _, tc := range tests {
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ require (
github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260429160308-91a892a60171
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828
github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0
github.com/smartcontractkit/chainlink-ton/deployment v0.0.0-20260430134932-681b7a7fe426
github.com/smartcontractkit/mcms v0.41.1
github.com/smartcontractkit/mcms v0.42.0
github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9
github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945
github.com/spf13/cast v1.10.0
Expand Down Expand Up @@ -247,6 +246,7 @@ require (
github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.19 // indirect
github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 // indirect
github.com/smartcontractkit/chainlink-ton v1.0.5-0.20260430134932-681b7a7fe426 // indirect
github.com/smartcontractkit/chainlink-ton/deployment v0.0.0-20260430134932-681b7a7fe426 // indirect
github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20260408092456-3c6369888d4a // indirect
github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -898,8 +898,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA=
github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d h1:PvXor5Fjer7FIONSqYXbpd1LkA14hWrlAyxXzOrC9t8=
github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d/go.mod h1:PLdNK6GlqfxIWXzziPkU7dCAVlVFeYkyyW7AQY0R+4Q=
github.com/smartcontractkit/mcms v0.41.1 h1:rK5X7if29gRhL6yqpUwxwaLYV0CqgwSJivdDqEJGFv4=
github.com/smartcontractkit/mcms v0.41.1/go.mod h1:9AJhwHSVwV2mETizHBNfEF9CemL/Fmf0yPxNGdTtL/0=
github.com/smartcontractkit/mcms v0.42.0 h1:zzs+auX6BL6sRIVpgVbLUviwrvWi8Fxo5dOP+9Wx/gk=
github.com/smartcontractkit/mcms v0.42.0/go.mod h1:39OxzRApGN7HG+JGbjxdCxyo5lvV0H0REUPyh3CzDGU=
github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9 h1:MOEuXYogv+RStASb8dWsyescu/xkigSi/Sv45NEjV7A=
github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9/go.mod h1:iwy4yWFuK+1JeoIRTaSOA9pl+8Kf//26zezxEXrAQEQ=
github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945 h1:zxcODLrFytOKmAd8ty8S/XK6WcIEJEgRBaL7sY/7l4Y=
Expand Down
Loading