From 6ae08ab7e676a15ec74db47921f1c7669a9e6a2a Mon Sep 17 00:00:00 2001 From: timothyF95 Date: Tue, 14 Apr 2026 10:11:41 +0100 Subject: [PATCH 1/6] Add registry list command --- cmd/registry/list/list.go | 46 +++++++++ cmd/registry/list/list_test.go | 174 +++++++++++++++++++++++++++++++++ cmd/registry/registry.go | 20 ++++ cmd/root.go | 8 ++ docs/cre.md | 1 + docs/cre_registry.md | 33 +++++++ docs/cre_registry_list.md | 38 +++++++ 7 files changed, 320 insertions(+) create mode 100644 cmd/registry/list/list.go create mode 100644 cmd/registry/list/list_test.go create mode 100644 cmd/registry/registry.go create mode 100644 docs/cre_registry.md create mode 100644 docs/cre_registry_list.md diff --git a/cmd/registry/list/list.go b/cmd/registry/list/list.go new file mode 100644 index 00000000..ae88ffdd --- /dev/null +++ b/cmd/registry/list/list.go @@ -0,0 +1,46 @@ +package list + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/ui" +) + +func New(runtimeContext *runtime.Context) *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "Lists available workflow registries for the current environment", + Long: `Displays the registries configured for your organization, including type and address.`, + Example: `cre registry list`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if runtimeContext.TenantContext == nil { + return fmt.Errorf("user context not available — run `cre login` and retry") + } + + registries := runtimeContext.TenantContext.Registries + if len(registries) == 0 { + ui.Warning("No registries found for this environment") + return nil + } + + ui.EnvContext(runtimeContext.EnvironmentSet.EnvLabel()) + ui.Line() + + for _, r := range registries { + ui.Bold(r.Label) + ui.Dim(fmt.Sprintf("ID: %s", r.ID)) + ui.Dim(fmt.Sprintf("Type: %s", r.Type)) + if r.Address != nil && *r.Address != "" { + ui.Dim(fmt.Sprintf("Addr: %s", *r.Address)) + } + ui.Line() + } + + return nil + }, + } +} diff --git a/cmd/registry/list/list_test.go b/cmd/registry/list/list_test.go new file mode 100644 index 00000000..c577194a --- /dev/null +++ b/cmd/registry/list/list_test.go @@ -0,0 +1,174 @@ +package list_test + +import ( + "io" + "os" + "strings" + "testing" + + "github.com/rs/zerolog" + + "github.com/smartcontractkit/cre-cli/cmd/registry/list" + "github.com/smartcontractkit/cre-cli/internal/environments" + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" +) + +func strPtr(s string) *string { return &s } + +func TestList_NoTenantContext(t *testing.T) { + logger := zerolog.New(io.Discard) + rtCtx := &runtime.Context{ + Logger: &logger, + EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"}, + TenantContext: nil, + } + + cmd := list.New(rtCtx) + cmd.SetArgs([]string{}) + err := cmd.Execute() + if err == nil { + t.Fatal("expected error when TenantContext is nil") + } + if !strings.Contains(err.Error(), "user context not available") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestList_EmptyRegistries(t *testing.T) { + logger := zerolog.New(io.Discard) + rtCtx := &runtime.Context{ + Logger: &logger, + EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"}, + TenantContext: &tenantctx.EnvironmentContext{ + Registries: []*tenantctx.Registry{}, + }, + } + + cmd := list.New(rtCtx) + cmd.SetArgs([]string{}) + + // suppress stderr (ui.Warning writes there) + oldStderr := os.Stderr + os.Stderr, _ = os.Open(os.DevNull) + defer func() { os.Stderr = oldStderr }() + + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestList_OnChainAndOffChain(t *testing.T) { + logger := zerolog.New(io.Discard) + rtCtx := &runtime.Context{ + Logger: &logger, + EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"}, + TenantContext: &tenantctx.EnvironmentContext{ + Registries: []*tenantctx.Registry{ + { + ID: "onchain:ethereum-testnet-sepolia", + Label: "ethereum-testnet-sepolia (0xaE55...1135)", + Type: "on-chain", + Address: strPtr("0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135"), + }, + { + ID: "private", + Label: "Private (Chainlink-hosted)", + Type: "off-chain", + }, + }, + }, + } + + cmd := list.New(rtCtx) + cmd.SetArgs([]string{}) + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + if err := cmd.Execute(); err != nil { + w.Close() + os.Stdout = oldStdout + t.Fatalf("unexpected error: %v", err) + } + + w.Close() + os.Stdout = oldStdout + var buf strings.Builder + _, _ = io.Copy(&buf, r) + output := buf.String() + + for _, want := range []string{ + "onchain:ethereum-testnet-sepolia", + "on-chain", + "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135", + "private", + "off-chain", + "Private (Chainlink-hosted)", + } { + if !strings.Contains(output, want) { + t.Errorf("output missing %q; full output:\n%s", want, output) + } + } +} + +func TestList_OffChainNoAddress(t *testing.T) { + logger := zerolog.New(io.Discard) + rtCtx := &runtime.Context{ + Logger: &logger, + EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"}, + TenantContext: &tenantctx.EnvironmentContext{ + Registries: []*tenantctx.Registry{ + { + ID: "private", + Label: "Private", + Type: "off-chain", + }, + }, + }, + } + + cmd := list.New(rtCtx) + cmd.SetArgs([]string{}) + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + if err := cmd.Execute(); err != nil { + w.Close() + os.Stdout = oldStdout + t.Fatalf("unexpected error: %v", err) + } + + w.Close() + os.Stdout = oldStdout + var buf strings.Builder + _, _ = io.Copy(&buf, r) + output := buf.String() + + if strings.Contains(output, "Addr:") { + t.Errorf("expected no Addr line for off-chain registry; output:\n%s", output) + } +} + +func TestList_RejectsArgs(t *testing.T) { + logger := zerolog.New(io.Discard) + rtCtx := &runtime.Context{ + Logger: &logger, + EnvironmentSet: &environments.EnvironmentSet{}, + TenantContext: &tenantctx.EnvironmentContext{}, + } + + cmd := list.New(rtCtx) + cmd.SetArgs([]string{"extra"}) + + // cobra prints usage to stderr on arg errors; suppress + cmd.SilenceUsage = true + cmd.SilenceErrors = true + + if err := cmd.Execute(); err == nil { + t.Fatal("expected error when extra args provided") + } +} diff --git a/cmd/registry/registry.go b/cmd/registry/registry.go new file mode 100644 index 00000000..440df4fe --- /dev/null +++ b/cmd/registry/registry.go @@ -0,0 +1,20 @@ +package registry + +import ( + "github.com/spf13/cobra" + + "github.com/smartcontractkit/cre-cli/cmd/registry/list" + "github.com/smartcontractkit/cre-cli/internal/runtime" +) + +func New(runtimeContext *runtime.Context) *cobra.Command { + registryCmd := &cobra.Command{ + Use: "registry", + Short: "Manages workflow registries", + Long: `The registry command lets you view and inspect the workflow registries available for your organization.`, + } + + registryCmd.AddCommand(list.New(runtimeContext)) + + return registryCmd +} diff --git a/cmd/root.go b/cmd/root.go index d70b735a..c506beb3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -19,6 +19,7 @@ import ( generatebindings "github.com/smartcontractkit/cre-cli/cmd/generate-bindings" "github.com/smartcontractkit/cre-cli/cmd/login" "github.com/smartcontractkit/cre-cli/cmd/logout" + "github.com/smartcontractkit/cre-cli/cmd/registry" "github.com/smartcontractkit/cre-cli/cmd/secrets" "github.com/smartcontractkit/cre-cli/cmd/templates" "github.com/smartcontractkit/cre-cli/cmd/update" @@ -389,17 +390,20 @@ func newRootCommand() *cobra.Command { whoamiCmd := whoami.New(runtimeContext) updateCmd := update.New(runtimeContext) templatesCmd := templates.New(runtimeContext) + registryCmd := registry.New(runtimeContext) secretsCmd.RunE = helpRunE workflowCmd.RunE = helpRunE accountCmd.RunE = helpRunE templatesCmd.RunE = helpRunE + registryCmd.RunE = helpRunE // Define groups (order controls display order) rootCmd.AddGroup(&cobra.Group{ID: "getting-started", Title: "Getting Started"}) rootCmd.AddGroup(&cobra.Group{ID: "account", Title: "Account"}) rootCmd.AddGroup(&cobra.Group{ID: "workflow", Title: "Workflow"}) rootCmd.AddGroup(&cobra.Group{ID: "secret", Title: "Secret"}) + rootCmd.AddGroup(&cobra.Group{ID: "registry", Title: "Registry"}) initCmd.GroupID = "getting-started" templatesCmd.GroupID = "getting-started" @@ -411,6 +415,7 @@ func newRootCommand() *cobra.Command { secretsCmd.GroupID = "secret" workflowCmd.GroupID = "workflow" + registryCmd.GroupID = "registry" rootCmd.AddCommand( initCmd, @@ -421,6 +426,7 @@ func newRootCommand() *cobra.Command { whoamiCmd, secretsCmd, workflowCmd, + registryCmd, genBindingsCmd, updateCmd, templatesCmd, @@ -457,6 +463,8 @@ func isLoadSettings(cmd *cobra.Command) bool { "cre templates list": {}, "cre templates add": {}, "cre templates remove": {}, + "cre registry": {}, + "cre registry list": {}, "cre": {}, } diff --git a/docs/cre.md b/docs/cre.md index 2ff0c534..51c21b28 100644 --- a/docs/cre.md +++ b/docs/cre.md @@ -28,6 +28,7 @@ cre [optional flags] * [cre init](cre_init.md) - Initialize a new cre project (recommended starting point) * [cre login](cre_login.md) - Start authentication flow * [cre logout](cre_logout.md) - Revoke authentication tokens and remove local credentials +* [cre registry](cre_registry.md) - Manages workflow registries * [cre secrets](cre_secrets.md) - Handles secrets management * [cre templates](cre_templates.md) - Manages template repository sources * [cre update](cre_update.md) - Update the cre CLI to the latest version diff --git a/docs/cre_registry.md b/docs/cre_registry.md new file mode 100644 index 00000000..7f586631 --- /dev/null +++ b/docs/cre_registry.md @@ -0,0 +1,33 @@ +## cre registry + +Manages workflow registries + +### Synopsis + +The registry command lets you view and inspect the workflow registries available for your organization. + +``` +cre registry [optional flags] +``` + +### Options + +``` + -h, --help help for registry +``` + +### Options inherited from parent commands + +``` + -e, --env string Path to .env file which contains sensitive info + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode +``` + +### SEE ALSO + +* [cre](cre.md) - CRE CLI tool +* [cre registry list](cre_registry_list.md) - Lists available workflow registries for the current environment + diff --git a/docs/cre_registry_list.md b/docs/cre_registry_list.md new file mode 100644 index 00000000..11f71f33 --- /dev/null +++ b/docs/cre_registry_list.md @@ -0,0 +1,38 @@ +## cre registry list + +Lists available workflow registries for the current environment + +### Synopsis + +Displays the registries configured for your organization, including type, address, and supported secrets auth flows. + +``` +cre registry list [optional flags] +``` + +### Examples + +``` +cre registry list +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + -e, --env string Path to .env file which contains sensitive info + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode +``` + +### SEE ALSO + +* [cre registry](cre_registry.md) - Manages workflow registries + From 026475f6801181dfd5b4996ef7d203867affba8a Mon Sep 17 00:00:00 2001 From: timothyF95 Date: Tue, 14 Apr 2026 10:20:22 +0100 Subject: [PATCH 2/6] Clean up --- cmd/registry/list/list.go | 3 ++- cmd/registry/list/list_test.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/registry/list/list.go b/cmd/registry/list/list.go index ae88ffdd..1ea5247d 100644 --- a/cmd/registry/list/list.go +++ b/cmd/registry/list/list.go @@ -27,7 +27,8 @@ func New(runtimeContext *runtime.Context) *cobra.Command { return nil } - ui.EnvContext(runtimeContext.EnvironmentSet.EnvLabel()) + ui.Line() + ui.Bold("Registries available to your organization") ui.Line() for _, r := range registries { diff --git a/cmd/registry/list/list_test.go b/cmd/registry/list/list_test.go index c577194a..131f3fbe 100644 --- a/cmd/registry/list/list_test.go +++ b/cmd/registry/list/list_test.go @@ -100,6 +100,7 @@ func TestList_OnChainAndOffChain(t *testing.T) { output := buf.String() for _, want := range []string{ + "Registries available to your organization", "onchain:ethereum-testnet-sepolia", "on-chain", "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135", From 19ea624e0248e0f2eb9e9dfc8103323b25847a9a Mon Sep 17 00:00:00 2001 From: timothyF95 Date: Tue, 14 Apr 2026 10:31:07 +0100 Subject: [PATCH 3/6] gendoc --- docs/cre_registry_list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cre_registry_list.md b/docs/cre_registry_list.md index 11f71f33..b27e7e21 100644 --- a/docs/cre_registry_list.md +++ b/docs/cre_registry_list.md @@ -4,7 +4,7 @@ Lists available workflow registries for the current environment ### Synopsis -Displays the registries configured for your organization, including type, address, and supported secrets auth flows. +Displays the registries configured for your organization, including type and address. ``` cre registry list [optional flags] From 4abd6676036781f5938b8efd31f839cec7dc0406 Mon Sep 17 00:00:00 2001 From: timothyF95 Date: Tue, 14 Apr 2026 12:38:20 +0100 Subject: [PATCH 4/6] Add deployment-registry setting to workflow commands --- cmd/root.go | 4 + cmd/workflow/activate/activate.go | 20 ++- cmd/workflow/delete/delete.go | 16 +- cmd/workflow/deploy/artifacts.go | 6 +- cmd/workflow/deploy/deploy.go | 12 +- cmd/workflow/deploy/register.go | 8 +- cmd/workflow/pause/pause.go | 18 +- internal/runtime/runtime_context.go | 35 +++- internal/settings/registry_resolution.go | 128 +++++++++++++ internal/settings/registry_resolution_test.go | 169 ++++++++++++++++++ internal/settings/settings_load.go | 1 + internal/settings/workflow_settings.go | 2 + 12 files changed, 383 insertions(+), 36 deletions(-) create mode 100644 internal/settings/registry_resolution.go create mode 100644 internal/settings/registry_resolution_test.go diff --git a/cmd/root.go b/cmd/root.go index c506beb3..a411aef7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -262,6 +262,10 @@ func newRootCommand() *cobra.Command { return fmt.Errorf("%w", err) } + if err := runtimeContext.AttachResolvedRegistry(); err != nil { + return err + } + // Restart spinner for remaining initialization if showSpinner { spinner = ui.NewSpinner() diff --git a/cmd/workflow/activate/activate.go b/cmd/workflow/activate/activate.go index 0eb759da..5c3ddd74 100644 --- a/cmd/workflow/activate/activate.go +++ b/cmd/workflow/activate/activate.go @@ -45,6 +45,12 @@ func New(runtimeContext *runtime.Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { handler := newHandler(runtimeContext) + if runtimeContext.ResolvedRegistry != nil { + if err := runtimeContext.ResolvedRegistry.RequireOnChainRegistry("activate"); err != nil { + return err + } + } + inputs, err := handler.ResolveInputs(runtimeContext.Viper) if err != nil { return err @@ -108,9 +114,9 @@ func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { return Inputs{ WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName, WorkflowOwner: h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, - DonFamily: h.environmentSet.DonFamily, - WorkflowRegistryContractAddress: h.environmentSet.WorkflowRegistryAddress, - WorkflowRegistryContractChainName: h.environmentSet.WorkflowRegistryChainName, + DonFamily: h.runtimeContext.ResolvedRegistry.DonFamily, + WorkflowRegistryContractAddress: h.runtimeContext.ResolvedRegistry.Address, + WorkflowRegistryContractChainName: h.runtimeContext.ResolvedRegistry.ChainName, }, nil } @@ -182,10 +188,10 @@ func (h *handler) Execute() error { switch txOut.Type { case client.Regular: ui.Success(fmt.Sprintf("Transaction confirmed: %s", txOut.Hash)) - ui.URL(fmt.Sprintf("%s/tx/%s", h.environmentSet.WorkflowRegistryChainExplorerURL, txOut.Hash)) + ui.URL(fmt.Sprintf("%s/tx/%s", h.runtimeContext.ResolvedRegistry.ExplorerURL, txOut.Hash)) ui.Line() ui.Success("Workflow activated successfully") - ui.Dim(fmt.Sprintf(" Contract address: %s", h.environmentSet.WorkflowRegistryAddress)) + ui.Dim(fmt.Sprintf(" Contract address: %s", h.runtimeContext.ResolvedRegistry.Address)) ui.Dim(fmt.Sprintf(" Transaction hash: %s", txOut.Hash)) ui.Dim(fmt.Sprintf(" Workflow Name: %s", workflowName)) ui.Dim(fmt.Sprintf(" Workflow ID: %s", hex.EncodeToString(latest.WorkflowId[:]))) @@ -207,9 +213,9 @@ func (h *handler) Execute() error { ui.Line() case client.Changeset: - chainSelector, err := settings.GetChainSelectorByChainName(h.environmentSet.WorkflowRegistryChainName) + chainSelector, err := settings.GetChainSelectorByChainName(h.runtimeContext.ResolvedRegistry.ChainName) if err != nil { - return fmt.Errorf("failed to get chain selector for chain %q: %w", h.environmentSet.WorkflowRegistryChainName, err) + return fmt.Errorf("failed to get chain selector for chain %q: %w", h.runtimeContext.ResolvedRegistry.ChainName, err) } mcmsConfig, err := settings.GetMCMSConfig(h.settings, chainSelector) if err != nil { diff --git a/cmd/workflow/delete/delete.go b/cmd/workflow/delete/delete.go index 78ee36e8..d36b61fa 100644 --- a/cmd/workflow/delete/delete.go +++ b/cmd/workflow/delete/delete.go @@ -44,6 +44,12 @@ func New(runtimeContext *runtime.Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { handler := newHandler(runtimeContext, cmd.InOrStdin()) + if runtimeContext.ResolvedRegistry != nil { + if err := runtimeContext.ResolvedRegistry.RequireOnChainRegistry("delete"); err != nil { + return err + } + } + inputs, err := handler.ResolveInputs(runtimeContext.Viper) if err != nil { return err @@ -114,8 +120,8 @@ func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName, WorkflowOwner: h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, SkipConfirmation: v.GetBool(settings.Flags.SkipConfirmation.Name), - WorkflowRegistryContractChainName: h.environmentSet.WorkflowRegistryChainName, - WorkflowRegistryContractAddress: h.environmentSet.WorkflowRegistryAddress, + WorkflowRegistryContractChainName: h.runtimeContext.ResolvedRegistry.ChainName, + WorkflowRegistryContractAddress: h.runtimeContext.ResolvedRegistry.Address, }, nil } @@ -193,7 +199,7 @@ func (h *handler) Execute() error { switch txOut.Type { case client.Regular: ui.Success("Transaction confirmed") - ui.URL(fmt.Sprintf("%s/tx/%s", h.environmentSet.WorkflowRegistryChainExplorerURL, txOut.Hash)) + ui.URL(fmt.Sprintf("%s/tx/%s", h.runtimeContext.ResolvedRegistry.ExplorerURL, txOut.Hash)) ui.Success(fmt.Sprintf("Deleted workflow ID: %s", hex.EncodeToString(wf.WorkflowId[:]))) case client.Raw: @@ -212,9 +218,9 @@ func (h *handler) Execute() error { ui.Line() case client.Changeset: - chainSelector, err := settings.GetChainSelectorByChainName(h.environmentSet.WorkflowRegistryChainName) + chainSelector, err := settings.GetChainSelectorByChainName(h.runtimeContext.ResolvedRegistry.ChainName) if err != nil { - return fmt.Errorf("failed to get chain selector for chain %q: %w", h.environmentSet.WorkflowRegistryChainName, err) + return fmt.Errorf("failed to get chain selector for chain %q: %w", h.runtimeContext.ResolvedRegistry.ChainName, err) } mcmsConfig, err := settings.GetMCMSConfig(h.settings, chainSelector) if err != nil { diff --git a/cmd/workflow/deploy/artifacts.go b/cmd/workflow/deploy/artifacts.go index 7e85e185..db4aaf10 100644 --- a/cmd/workflow/deploy/artifacts.go +++ b/cmd/workflow/deploy/artifacts.go @@ -33,12 +33,12 @@ func (h *handler) uploadArtifacts() error { gql := graphqlclient.New(h.credentials, h.environmentSet, h.log) - chainSelector, err := settings.GetChainSelectorByChainName(h.environmentSet.WorkflowRegistryChainName) + chainSelector, err := settings.GetChainSelectorByChainName(h.runtimeContext.ResolvedRegistry.ChainName) if err != nil { - return fmt.Errorf("failed to get chain selector for chain %q: %w", h.environmentSet.WorkflowRegistryChainName, err) + return fmt.Errorf("failed to get chain selector for chain %q: %w", h.runtimeContext.ResolvedRegistry.ChainName, err) } - storageClient := storageclient.New(gql, h.environmentSet.WorkflowRegistryAddress, h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, chainSelector, h.log) + storageClient := storageclient.New(gql, h.runtimeContext.ResolvedRegistry.Address, h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, chainSelector, h.log) if h.settings.StorageSettings.CREStorage.ServiceTimeout != 0 { storageClient.SetServiceTimeout(h.settings.StorageSettings.CREStorage.ServiceTimeout) } diff --git a/cmd/workflow/deploy/deploy.go b/cmd/workflow/deploy/deploy.go index 843da5d4..8c5df691 100644 --- a/cmd/workflow/deploy/deploy.go +++ b/cmd/workflow/deploy/deploy.go @@ -95,6 +95,12 @@ func New(runtimeContext *runtime.Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { h := newHandler(runtimeContext, cmd.InOrStdin()) + if runtimeContext.ResolvedRegistry != nil { + if err := runtimeContext.ResolvedRegistry.RequireOnChainRegistry("deploy"); err != nil { + return err + } + } + inputs, err := h.ResolveInputs(runtimeContext.Viper) if err != nil { return err @@ -173,7 +179,7 @@ func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { WorkflowOwner: h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, WorkflowTag: workflowTag, ConfigURL: configURL, - DonFamily: h.environmentSet.DonFamily, + DonFamily: h.runtimeContext.ResolvedRegistry.DonFamily, WorkflowPath: h.settings.Workflow.WorkflowArtifactSettings.WorkflowPath, KeepAlive: false, @@ -182,8 +188,8 @@ func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { OutputPath: v.GetString("output"), WasmPath: v.GetString("wasm"), - WorkflowRegistryContractChainName: h.environmentSet.WorkflowRegistryChainName, - WorkflowRegistryContractAddress: h.environmentSet.WorkflowRegistryAddress, + WorkflowRegistryContractChainName: h.runtimeContext.ResolvedRegistry.ChainName, + WorkflowRegistryContractAddress: h.runtimeContext.ResolvedRegistry.Address, OwnerLabel: v.GetString("owner-label"), SkipConfirmation: v.GetBool(settings.Flags.SkipConfirmation.Name), SkipTypeChecks: v.GetBool(cmdcommon.SkipTypeChecksCLIFlag), diff --git a/cmd/workflow/deploy/register.go b/cmd/workflow/deploy/register.go index 4042c9db..e6a57be3 100644 --- a/cmd/workflow/deploy/register.go +++ b/cmd/workflow/deploy/register.go @@ -68,12 +68,12 @@ func (h *handler) handleUpsert(params client.RegisterWorkflowV2Parameters) error switch txOut.Type { case client.Regular: ui.Success("Transaction confirmed") - ui.URL(fmt.Sprintf("%s/tx/%s", h.environmentSet.WorkflowRegistryChainExplorerURL, txOut.Hash)) + ui.URL(fmt.Sprintf("%s/tx/%s", h.runtimeContext.ResolvedRegistry.ExplorerURL, txOut.Hash)) ui.Line() ui.Success("Workflow deployed successfully") ui.Line() ui.Bold("Details:") - ui.Dim(fmt.Sprintf(" Contract address: %s", h.environmentSet.WorkflowRegistryAddress)) + ui.Dim(fmt.Sprintf(" Contract address: %s", h.runtimeContext.ResolvedRegistry.Address)) ui.Dim(fmt.Sprintf(" Transaction hash: %s", txOut.Hash)) ui.Dim(fmt.Sprintf(" Workflow Name: %s", workflowName)) ui.Dim(fmt.Sprintf(" Workflow ID: %s", h.workflowArtifact.WorkflowID)) @@ -99,9 +99,9 @@ func (h *handler) handleUpsert(params client.RegisterWorkflowV2Parameters) error ui.Line() case client.Changeset: - chainSelector, err := settings.GetChainSelectorByChainName(h.environmentSet.WorkflowRegistryChainName) + chainSelector, err := settings.GetChainSelectorByChainName(h.runtimeContext.ResolvedRegistry.ChainName) if err != nil { - return fmt.Errorf("failed to get chain selector for chain %q: %w", h.environmentSet.WorkflowRegistryChainName, err) + return fmt.Errorf("failed to get chain selector for chain %q: %w", h.runtimeContext.ResolvedRegistry.ChainName, err) } mcmsConfig, err := settings.GetMCMSConfig(h.settings, chainSelector) if err != nil { diff --git a/cmd/workflow/pause/pause.go b/cmd/workflow/pause/pause.go index a1564764..00204dcd 100644 --- a/cmd/workflow/pause/pause.go +++ b/cmd/workflow/pause/pause.go @@ -45,6 +45,12 @@ func New(runtimeContext *runtime.Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { handler := newHandler(runtimeContext) + if runtimeContext.ResolvedRegistry != nil { + if err := runtimeContext.ResolvedRegistry.RequireOnChainRegistry("pause"); err != nil { + return err + } + } + inputs, err := handler.ResolveInputs(runtimeContext.Viper) if err != nil { return err @@ -107,8 +113,8 @@ func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { return Inputs{ WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName, WorkflowOwner: h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, - WorkflowRegistryContractChainName: h.environmentSet.WorkflowRegistryChainName, - WorkflowRegistryContractAddress: h.environmentSet.WorkflowRegistryAddress, + WorkflowRegistryContractChainName: h.runtimeContext.ResolvedRegistry.ChainName, + WorkflowRegistryContractAddress: h.runtimeContext.ResolvedRegistry.Address, }, nil } @@ -176,11 +182,11 @@ func (h *handler) Execute() error { switch txOut.Type { case client.Regular: ui.Success("Transaction confirmed") - ui.URL(fmt.Sprintf("%s/tx/%s", h.environmentSet.WorkflowRegistryChainExplorerURL, txOut.Hash)) + ui.URL(fmt.Sprintf("%s/tx/%s", h.runtimeContext.ResolvedRegistry.ExplorerURL, txOut.Hash)) ui.Success("Workflows paused successfully") ui.Line() ui.Bold("Details:") - ui.Dim(fmt.Sprintf(" Contract address: %s", h.environmentSet.WorkflowRegistryAddress)) + ui.Dim(fmt.Sprintf(" Contract address: %s", h.runtimeContext.ResolvedRegistry.Address)) ui.Dim(fmt.Sprintf(" Transaction hash: %s", txOut.Hash)) ui.Dim(fmt.Sprintf(" Workflow Name: %s", workflowName)) for _, w := range activeWorkflowIDs { @@ -204,9 +210,9 @@ func (h *handler) Execute() error { ui.Line() case client.Changeset: - chainSelector, err := settings.GetChainSelectorByChainName(h.environmentSet.WorkflowRegistryChainName) + chainSelector, err := settings.GetChainSelectorByChainName(h.runtimeContext.ResolvedRegistry.ChainName) if err != nil { - return fmt.Errorf("failed to get chain selector for chain %q: %w", h.environmentSet.WorkflowRegistryChainName, err) + return fmt.Errorf("failed to get chain selector for chain %q: %w", h.runtimeContext.ResolvedRegistry.ChainName, err) } mcmsConfig, err := settings.GetMCMSConfig(h.settings, chainSelector) if err != nil { diff --git a/internal/runtime/runtime_context.go b/internal/runtime/runtime_context.go index af367838..0450fe85 100644 --- a/internal/runtime/runtime_context.go +++ b/internal/runtime/runtime_context.go @@ -23,14 +23,15 @@ var ( ) type Context struct { - Logger *zerolog.Logger - Viper *viper.Viper - ClientFactory client.Factory - Settings *settings.Settings - Credentials *credentials.Credentials - EnvironmentSet *environments.EnvironmentSet - TenantContext *tenantctx.EnvironmentContext - Workflow WorkflowRuntime + Logger *zerolog.Logger + Viper *viper.Viper + ClientFactory client.Factory + Settings *settings.Settings + Credentials *credentials.Credentials + EnvironmentSet *environments.EnvironmentSet + TenantContext *tenantctx.EnvironmentContext + ResolvedRegistry *settings.ResolvedRegistry + Workflow WorkflowRuntime } type WorkflowRuntime struct { @@ -111,6 +112,24 @@ func (ctx *Context) AttachTenantContext(validationCtx context.Context) error { return nil } +// AttachResolvedRegistry resolves the deployment-registry from workflow +// settings against the tenant context registries. Must be called after +// AttachSettings and AttachTenantContext. +func (ctx *Context) AttachResolvedRegistry() error { + deploymentRegistry := "" + if ctx.Settings != nil { + deploymentRegistry = ctx.Settings.Workflow.UserWorkflowSettings.DeploymentRegistry + } + + resolved, err := settings.ResolveRegistry(deploymentRegistry, ctx.TenantContext, ctx.EnvironmentSet) + if err != nil { + return fmt.Errorf("failed to resolve deployment registry: %w", err) + } + + ctx.ResolvedRegistry = resolved + return nil +} + func (ctx *Context) AttachEnvironmentSet() error { var err error diff --git a/internal/settings/registry_resolution.go b/internal/settings/registry_resolution.go new file mode 100644 index 00000000..5020447f --- /dev/null +++ b/internal/settings/registry_resolution.go @@ -0,0 +1,128 @@ +package settings + +import ( + "fmt" + "strconv" + "strings" + + "github.com/smartcontractkit/cre-cli/internal/environments" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" +) + +// ResolvedRegistry holds the fully resolved registry configuration for a +// workflow command. It is built from either the static EnvironmentSet defaults +// or from a tenant context registry entry selected via deployment-registry. +type ResolvedRegistry struct { + ID string + Type string // "on-chain" or "off-chain" + Address string + ChainName string + DonFamily string + ExplorerURL string +} + +// ResolveRegistry maps an optional deployment-registry value to a concrete +// ResolvedRegistry. When deploymentRegistry is empty the static EnvironmentSet +// values are used (backwards-compatible default). When set, it is looked up in +// tenantCtx.Registries and the matching entry is used. Off-chain registries +// are rejected in production environments. +func ResolveRegistry( + deploymentRegistry string, + tenantCtx *tenantctx.EnvironmentContext, + envSet *environments.EnvironmentSet, +) (*ResolvedRegistry, error) { + if deploymentRegistry == "" { + return defaultFromEnvironmentSet(envSet), nil + } + + if tenantCtx == nil { + return nil, fmt.Errorf("deployment-registry %q is set but user context is not available — run `cre login` and retry", deploymentRegistry) + } + + reg := findRegistry(tenantCtx.Registries, deploymentRegistry) + if reg == nil { + return nil, fmt.Errorf("registry %q not found in context.yaml; available: [%s]", + deploymentRegistry, availableIDs(tenantCtx.Registries)) + } + + if reg.Type == "off-chain" { + if isProduction(envSet) { + return nil, fmt.Errorf("off-chain (private) registries are not yet supported in production") + } + return &ResolvedRegistry{ + ID: reg.ID, + Type: reg.Type, + DonFamily: tenantCtx.DefaultDonFamily, + }, nil + } + + resolved := &ResolvedRegistry{ + ID: reg.ID, + Type: reg.Type, + DonFamily: tenantCtx.DefaultDonFamily, + ExplorerURL: envSet.WorkflowRegistryChainExplorerURL, + } + + if reg.Address != nil { + resolved.Address = *reg.Address + } + + if reg.ChainSelector != nil { + sel, err := strconv.ParseUint(*reg.ChainSelector, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid chain_selector %q for registry %q: %w", *reg.ChainSelector, reg.ID, err) + } + name, err := GetChainNameByChainSelector(sel) + if err != nil { + return nil, fmt.Errorf("cannot resolve chain name for selector %d (registry %q): %w", sel, reg.ID, err) + } + resolved.ChainName = name + } + + return resolved, nil +} + +// TODO: remove this once off-chain routing is implemented +// RequireOnChainRegistry returns an error if the resolved registry is not +// on-chain. Use this in commands that only support on-chain workflow registries +// (deploy, pause, activate, delete) until off-chain routing is implemented. +func (r *ResolvedRegistry) RequireOnChainRegistry(commandName string) error { + if r.Type != "on-chain" { + return fmt.Errorf( + "%s currently only supports on-chain registries; deployment-registry %q is %s", + commandName, r.ID, r.Type, + ) + } + return nil +} + +func defaultFromEnvironmentSet(envSet *environments.EnvironmentSet) *ResolvedRegistry { + return &ResolvedRegistry{ + Type: "on-chain", + Address: envSet.WorkflowRegistryAddress, + ChainName: envSet.WorkflowRegistryChainName, + DonFamily: envSet.DonFamily, + ExplorerURL: envSet.WorkflowRegistryChainExplorerURL, + } +} + +func findRegistry(registries []*tenantctx.Registry, id string) *tenantctx.Registry { + for _, r := range registries { + if r.ID == id { + return r + } + } + return nil +} + +func availableIDs(registries []*tenantctx.Registry) string { + ids := make([]string, 0, len(registries)) + for _, r := range registries { + ids = append(ids, r.ID) + } + return strings.Join(ids, ", ") +} + +func isProduction(envSet *environments.EnvironmentSet) bool { + return envSet.EnvName == "" || envSet.EnvName == environments.DefaultEnv +} diff --git a/internal/settings/registry_resolution_test.go b/internal/settings/registry_resolution_test.go new file mode 100644 index 00000000..2ca37c9b --- /dev/null +++ b/internal/settings/registry_resolution_test.go @@ -0,0 +1,169 @@ +package settings + +import ( + "strings" + "testing" + + "github.com/smartcontractkit/cre-cli/internal/environments" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" +) + +func strPtr(s string) *string { return &s } + +func stagingEnvSet() *environments.EnvironmentSet { + return &environments.EnvironmentSet{ + EnvName: "STAGING", + WorkflowRegistryAddress: "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135", + WorkflowRegistryChainName: "ethereum-testnet-sepolia", + WorkflowRegistryChainExplorerURL: "https://sepolia.etherscan.io", + DonFamily: "zone-a", + } +} + +func prodEnvSet() *environments.EnvironmentSet { + return &environments.EnvironmentSet{ + EnvName: "PRODUCTION", + WorkflowRegistryAddress: "0x4Ac54353FA4Fa961AfcC5ec4B118596d3305E7e5", + WorkflowRegistryChainName: "ethereum-mainnet", + WorkflowRegistryChainExplorerURL: "https://etherscan.io", + DonFamily: "zone-a", + } +} + +func sampleTenantCtx() *tenantctx.EnvironmentContext { + return &tenantctx.EnvironmentContext{ + DefaultDonFamily: "zone-a", + Registries: []*tenantctx.Registry{ + { + ID: "onchain:ethereum-testnet-sepolia", + Label: "ethereum-testnet-sepolia (0xaE55...1135)", + Type: "on-chain", + ChainSelector: strPtr("16015286601757825753"), + Address: strPtr("0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135"), + }, + { + ID: "private", + Label: "Private (Chainlink-hosted)", + Type: "off-chain", + }, + }, + } +} + +func TestResolveRegistry_EmptyFallsBackToEnvSet(t *testing.T) { + envSet := stagingEnvSet() + resolved, err := ResolveRegistry("", nil, envSet) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resolved.Type != "on-chain" { + t.Errorf("expected on-chain, got %s", resolved.Type) + } + if resolved.Address != envSet.WorkflowRegistryAddress { + t.Errorf("expected address %s, got %s", envSet.WorkflowRegistryAddress, resolved.Address) + } + if resolved.ChainName != envSet.WorkflowRegistryChainName { + t.Errorf("expected chain %s, got %s", envSet.WorkflowRegistryChainName, resolved.ChainName) + } + if resolved.DonFamily != envSet.DonFamily { + t.Errorf("expected don %s, got %s", envSet.DonFamily, resolved.DonFamily) + } + if resolved.ExplorerURL != envSet.WorkflowRegistryChainExplorerURL { + t.Errorf("expected explorer %s, got %s", envSet.WorkflowRegistryChainExplorerURL, resolved.ExplorerURL) + } +} + +func TestResolveRegistry_OnChainFromContext(t *testing.T) { + resolved, err := ResolveRegistry("onchain:ethereum-testnet-sepolia", sampleTenantCtx(), stagingEnvSet()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resolved.Type != "on-chain" { + t.Errorf("expected on-chain, got %s", resolved.Type) + } + if resolved.Address != "0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135" { + t.Errorf("unexpected address: %s", resolved.Address) + } + if resolved.ChainName != "ethereum-testnet-sepolia" { + t.Errorf("unexpected chain name: %s", resolved.ChainName) + } + if resolved.DonFamily != "zone-a" { + t.Errorf("unexpected don family: %s", resolved.DonFamily) + } +} + +func TestResolveRegistry_OffChainFromContext(t *testing.T) { + resolved, err := ResolveRegistry("private", sampleTenantCtx(), stagingEnvSet()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resolved.Type != "off-chain" { + t.Errorf("expected off-chain, got %s", resolved.Type) + } + if resolved.Address != "" { + t.Errorf("expected empty address for off-chain, got %s", resolved.Address) + } + if resolved.ChainName != "" { + t.Errorf("expected empty chain for off-chain, got %s", resolved.ChainName) + } + if resolved.DonFamily != "zone-a" { + t.Errorf("unexpected don family: %s", resolved.DonFamily) + } +} + +func TestResolveRegistry_UnknownID(t *testing.T) { + _, err := ResolveRegistry("does-not-exist", sampleTenantCtx(), stagingEnvSet()) + if err == nil { + t.Fatal("expected error for unknown registry ID") + } + if !strings.Contains(err.Error(), "not found in context.yaml") { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(err.Error(), "onchain:ethereum-testnet-sepolia") { + t.Errorf("error should list available IDs: %v", err) + } +} + +func TestResolveRegistry_NilTenantContextWithID(t *testing.T) { + _, err := ResolveRegistry("private", nil, stagingEnvSet()) + if err == nil { + t.Fatal("expected error when TenantContext is nil with a registry ID set") + } + if !strings.Contains(err.Error(), "user context is not available") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestResolveRegistry_OffChainBlockedInProduction(t *testing.T) { + _, err := ResolveRegistry("private", sampleTenantCtx(), prodEnvSet()) + if err == nil { + t.Fatal("expected error for off-chain in production") + } + if !strings.Contains(err.Error(), "not yet supported in production") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestResolveRegistry_OffChainBlockedWhenEnvEmpty(t *testing.T) { + envSet := stagingEnvSet() + envSet.EnvName = "" + _, err := ResolveRegistry("private", sampleTenantCtx(), envSet) + if err == nil { + t.Fatal("expected error for off-chain when env name is empty (defaults to production)") + } + if !strings.Contains(err.Error(), "not yet supported in production") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestRequireOnChainRegistry(t *testing.T) { + onChain := &ResolvedRegistry{ID: "onchain:ethereum-testnet-sepolia", Type: "on-chain"} + if err := onChain.RequireOnChainRegistry("deploy"); err != nil { + t.Errorf("on-chain should pass: %v", err) + } + + offChain := &ResolvedRegistry{ID: "private", Type: "off-chain"} + if err := offChain.RequireOnChainRegistry("deploy"); err == nil { + t.Error("off-chain should be rejected") + } +} diff --git a/internal/settings/settings_load.go b/internal/settings/settings_load.go index ed193351..61710f9c 100644 --- a/internal/settings/settings_load.go +++ b/internal/settings/settings_load.go @@ -21,6 +21,7 @@ const ( SethConfigPathSettingName = "logging.seth-config-path" RegistriesSettingName = "contracts.registries" KeystoneSettingName = "contracts.keystone" + DeploymentRegistrySettingName = "user-workflow.deployment-registry" RpcsSettingName = "rpcs" ExperimentalChainsSettingName = "experimental-chains" // used by simulator when present in target config ) diff --git a/internal/settings/workflow_settings.go b/internal/settings/workflow_settings.go index fd43c4bf..43488ae6 100644 --- a/internal/settings/workflow_settings.go +++ b/internal/settings/workflow_settings.go @@ -84,6 +84,7 @@ type WorkflowSettings struct { WorkflowOwnerAddress string `mapstructure:"workflow-owner-address" yaml:"workflow-owner-address"` WorkflowOwnerType string `mapstructure:"workflow-owner-type" yaml:"workflow-owner-type"` WorkflowName string `mapstructure:"workflow-name" yaml:"workflow-name"` + DeploymentRegistry string `mapstructure:"deployment-registry" yaml:"deployment-registry"` } `mapstructure:"user-workflow" yaml:"user-workflow"` WorkflowArtifactSettings struct { WorkflowPath string `mapstructure:"workflow-path" yaml:"workflow-path"` @@ -128,6 +129,7 @@ func loadWorkflowSettings(logger *zerolog.Logger, v *viper.Viper, cmd *cobra.Com } workflowSettings.UserWorkflowSettings.WorkflowName = getSetting(WorkflowNameSettingName) + workflowSettings.UserWorkflowSettings.DeploymentRegistry = getSetting(DeploymentRegistrySettingName) workflowSettings.WorkflowArtifactSettings.WorkflowPath = getSetting(WorkflowPathSettingName) workflowSettings.WorkflowArtifactSettings.ConfigPath = getSetting(ConfigPathSettingName) workflowSettings.WorkflowArtifactSettings.SecretsPath = getSetting(SecretsPathSettingName) From dd99cff6f7baf687dc9483dc5aafdacb78633a05 Mon Sep 17 00:00:00 2001 From: timothyF95 Date: Tue, 14 Apr 2026 13:12:55 +0100 Subject: [PATCH 5/6] fix import cycle --- cmd/workflow/deploy/compile_test.go | 1 + cmd/workflow/simulate/simulate_test.go | 1 + internal/testutil/chainsim/simulated_environment.go | 3 ++- internal/testutil/{ => testsettings}/test_settings.go | 2 +- .../testutil/{ => testsettings}/testdata/test-project.yaml | 0 .../testutil/{ => testsettings}/testdata/test-workflow.yaml | 0 6 files changed, 5 insertions(+), 2 deletions(-) rename internal/testutil/{ => testsettings}/test_settings.go (98%) rename internal/testutil/{ => testsettings}/testdata/test-project.yaml (100%) rename internal/testutil/{ => testsettings}/testdata/test-workflow.yaml (100%) diff --git a/cmd/workflow/deploy/compile_test.go b/cmd/workflow/deploy/compile_test.go index aaa87ac3..33c5ce83 100644 --- a/cmd/workflow/deploy/compile_test.go +++ b/cmd/workflow/deploy/compile_test.go @@ -238,6 +238,7 @@ func createTestSettings(workflowOwnerAddress, workflowOwnerType, workflowName, w WorkflowOwnerAddress string `mapstructure:"workflow-owner-address" yaml:"workflow-owner-address"` WorkflowOwnerType string `mapstructure:"workflow-owner-type" yaml:"workflow-owner-type"` WorkflowName string `mapstructure:"workflow-name" yaml:"workflow-name"` + DeploymentRegistry string `mapstructure:"deployment-registry" yaml:"deployment-registry"` }{ WorkflowOwnerAddress: workflowOwnerAddress, WorkflowOwnerType: workflowOwnerType, diff --git a/cmd/workflow/simulate/simulate_test.go b/cmd/workflow/simulate/simulate_test.go index b29d3f52..d5635077 100644 --- a/cmd/workflow/simulate/simulate_test.go +++ b/cmd/workflow/simulate/simulate_test.go @@ -111,6 +111,7 @@ func createSimulateTestSettings(workflowName, workflowPath, configPath string) * WorkflowOwnerAddress string `mapstructure:"workflow-owner-address" yaml:"workflow-owner-address"` WorkflowOwnerType string `mapstructure:"workflow-owner-type" yaml:"workflow-owner-type"` WorkflowName string `mapstructure:"workflow-name" yaml:"workflow-name"` + DeploymentRegistry string `mapstructure:"deployment-registry" yaml:"deployment-registry"` }{ WorkflowName: workflowName, }, diff --git a/internal/testutil/chainsim/simulated_environment.go b/internal/testutil/chainsim/simulated_environment.go index 4b2d012c..948fb472 100644 --- a/internal/testutil/chainsim/simulated_environment.go +++ b/internal/testutil/chainsim/simulated_environment.go @@ -14,6 +14,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/runtime" "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/testutil" + "github.com/smartcontractkit/cre-cli/internal/testutil/testsettings" ) type SimulatedEnvironment struct { @@ -62,7 +63,7 @@ func (se *SimulatedEnvironment) Close() { func (se *SimulatedEnvironment) createContextWithLogger(logger *zerolog.Logger) *runtime.Context { v := viper.New() v.Set(settings.EthPrivateKeyEnvVar, TestPrivateKey) - settings, err := testutil.NewTestSettings(v, logger) + settings, err := testsettings.NewTestSettings(v, logger) if err != nil { logger.Warn().Err(err).Msg("failed to create new test settings") } diff --git a/internal/testutil/test_settings.go b/internal/testutil/testsettings/test_settings.go similarity index 98% rename from internal/testutil/test_settings.go rename to internal/testutil/testsettings/test_settings.go index cd3dcc43..d3f4148c 100644 --- a/internal/testutil/test_settings.go +++ b/internal/testutil/testsettings/test_settings.go @@ -1,4 +1,4 @@ -package testutil +package testsettings import ( _ "embed" diff --git a/internal/testutil/testdata/test-project.yaml b/internal/testutil/testsettings/testdata/test-project.yaml similarity index 100% rename from internal/testutil/testdata/test-project.yaml rename to internal/testutil/testsettings/testdata/test-project.yaml diff --git a/internal/testutil/testdata/test-workflow.yaml b/internal/testutil/testsettings/testdata/test-workflow.yaml similarity index 100% rename from internal/testutil/testdata/test-workflow.yaml rename to internal/testutil/testsettings/testdata/test-workflow.yaml From faee3654f48d8d66b82f0ebc398ced912010653c Mon Sep 17 00:00:00 2001 From: timothyF95 Date: Tue, 14 Apr 2026 13:23:47 +0100 Subject: [PATCH 6/6] fix simulated env --- .../chainsim/simulated_environment.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/testutil/chainsim/simulated_environment.go b/internal/testutil/chainsim/simulated_environment.go index 948fb472..8e82163e 100644 --- a/internal/testutil/chainsim/simulated_environment.go +++ b/internal/testutil/chainsim/simulated_environment.go @@ -12,7 +12,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/credentials" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/runtime" - "github.com/smartcontractkit/cre-cli/internal/settings" + settingspkg "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/testutil" "github.com/smartcontractkit/cre-cli/internal/testutil/testsettings" ) @@ -62,7 +62,7 @@ func (se *SimulatedEnvironment) Close() { func (se *SimulatedEnvironment) createContextWithLogger(logger *zerolog.Logger) *runtime.Context { v := viper.New() - v.Set(settings.EthPrivateKeyEnvVar, TestPrivateKey) + v.Set(settingspkg.EthPrivateKeyEnvVar, TestPrivateKey) settings, err := testsettings.NewTestSettings(v, logger) if err != nil { logger.Warn().Err(err).Msg("failed to create new test settings") @@ -80,13 +80,16 @@ func (se *SimulatedEnvironment) createContextWithLogger(logger *zerolog.Logger) logger.Warn().Err(err).Msg("failed to create new credentials") } + resolved, _ := settingspkg.ResolveRegistry("", nil, environmentSet) + ctx := &runtime.Context{ - Logger: logger, - Viper: v, - ClientFactory: simulatedFactory, - Settings: settings, - EnvironmentSet: environmentSet, - Credentials: creds, + Logger: logger, + Viper: v, + ClientFactory: simulatedFactory, + Settings: settings, + EnvironmentSet: environmentSet, + Credentials: creds, + ResolvedRegistry: resolved, } // Mark credentials as validated for tests to bypass validation