From 233a5176682ea59de67e1d2e1f1821efe2953c61 Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Wed, 21 Jan 2026 18:51:36 -0400 Subject: [PATCH 1/8] add top-level address-book CLI commands --- engine/cld/legacy/cli/commands/addressbook.go | 184 ++++++++++++++++++ .../legacy/cli/commands/addressbook_test.go | 117 +++++++++++ 2 files changed, 301 insertions(+) create mode 100644 engine/cld/legacy/cli/commands/addressbook.go create mode 100644 engine/cld/legacy/cli/commands/addressbook_test.go diff --git a/engine/cld/legacy/cli/commands/addressbook.go b/engine/cld/legacy/cli/commands/addressbook.go new file mode 100644 index 00000000..8a4104ab --- /dev/null +++ b/engine/cld/legacy/cli/commands/addressbook.go @@ -0,0 +1,184 @@ +package commands + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/domain" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/legacy/cli" +) + +// NewAddressBookCmds creates a new set of commands for address book operations. +func (c Commands) NewAddressBookCmds(domain domain.Domain) *cobra.Command { + addressBookCmd := &cobra.Command{ + Use: "address-book", + Short: "Address book operations", + } + + addressBookCmd.AddCommand(c.newAddressBookMerge(domain)) + addressBookCmd.AddCommand(c.newAddressBookMigrate(domain)) + addressBookCmd.AddCommand(c.newAddressBookRemove(domain)) + + addressBookCmd.PersistentFlags().StringP("environment", "e", "", "Deployment environment (required)") + err := addressBookCmd.MarkPersistentFlagRequired("environment") + if err != nil { + return nil + } + + return addressBookCmd +} + +var ( + addressBookMergeLong = cli.LongDesc(` + Merges the address book artifact of a specific changeset to the main address book within a + given Domain Environment. This is to ensure that the address book is up-to-date with the + latest changeset changes. + `) + + addressBookMergeExample = cli.Examples(` + # Merge the address book for the 0001_deploy_cap changeset in the ccip staging domain environment + ccip address-book merge --environment staging --name 0001_deploy_cap + + # Merge with a specific durable pipeline timestamp + ccip address-book merge --environment staging --name 0001_deploy_cap --timestamp 1234567890 + `) +) + +// newAddressBookMerge creates a command to merge the address books for a changeset to +// the main address book within a given domain environment. +func (Commands) newAddressBookMerge(domain domain.Domain) *cobra.Command { + var ( + name string + timestamp string + ) + + cmd := cobra.Command{ + Use: "merge", + Short: "Merge the address book for a changeset to the main address book", + Long: addressBookMergeLong, + Example: addressBookMergeExample, + RunE: func(cmd *cobra.Command, args []string) error { + envKey, _ := cmd.Flags().GetString("environment") + envDir := domain.EnvDir(envKey) + + if err := envDir.MergeMigrationAddressBook(name, timestamp); err != nil { + return fmt.Errorf("error during address book merge for %s %s %s: %w", + domain, envKey, name, err, + ) + } + + cmd.Printf("Merged address books for %s %s %s", + domain, envKey, name, + ) + + return nil + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "name (required)") + cmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "Durable Pipeline timestamp (optional)") + + err := cmd.MarkFlagRequired("name") + if err != nil { + return nil + } + + return &cmd +} + +var ( + addressBookMigrateLong = cli.LongDesc(` + Converts the address book artifact format to the new datastore schema within a + given Domain Environment. This updates your on-chain address book to the latest storage format. + `) + + addressBookMigrateExample = cli.Examples(` + # Migrate the address book for the ccip staging domain to the new datastore format + ccip address-book migrate --environment staging + `) +) + +// newAddressBookMigrate creates a command to convert the address book +// artifact to the new datastore format within a given domain environment. +func (Commands) newAddressBookMigrate(domain domain.Domain) *cobra.Command { + cmd := cobra.Command{ + Use: "migrate", + Short: "Migrate address book to the new datastore format", + Long: addressBookMigrateLong, + Example: addressBookMigrateExample, + RunE: func(cmd *cobra.Command, args []string) error { + envKey, _ := cmd.Flags().GetString("environment") + envDir := domain.EnvDir(envKey) + + if err := envDir.MigrateAddressBook(); err != nil { + return fmt.Errorf("error during address book conversion for %s %s: %w", + domain, envKey, err, + ) + } + + cmd.Printf("Address book for %s %s successfully migrated to the new datastore format", + domain, envKey, + ) + + return nil + }, + } + + return &cmd +} + +var ( + addressBookRemoveLong = cli.LongDesc(` + Removes the address book entries introduced by a specific changeset from the main + address book within a given Domain Environment. This can be used to rollback + address-book merge changes. + `) + + addressBookRemoveExample = cli.Examples(` + # Remove the address book entries for the 0001_deploy_cap changeset in the ccip staging domain + ccip address-book remove --environment staging --name 0001_deploy_cap + `) +) + +// newAddressBookRemove creates a command to remove a changeset's +// address book entries from the main address book within a given domain environment. +func (Commands) newAddressBookRemove(domain domain.Domain) *cobra.Command { + var ( + name string + timestamp string + ) + + cmd := cobra.Command{ + Use: "remove", + Short: "Remove changeset address book entries", + Long: addressBookRemoveLong, + Example: addressBookRemoveExample, + RunE: func(cmd *cobra.Command, args []string) error { + envKey, _ := cmd.Flags().GetString("environment") + envDir := domain.EnvDir(envKey) + + if err := envDir.RemoveMigrationAddressBook(name, timestamp); err != nil { + return fmt.Errorf("error during address book remove for %s %s %s: %w", + domain, envKey, name, err, + ) + } + + cmd.Printf("Removed address books for %s %s %s", + domain, envKey, name, + ) + + return nil + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "name (required)") + cmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "Durable Pipeline timestamp (optional)") + + err := cmd.MarkFlagRequired("name") + if err != nil { + return nil + } + + return &cmd +} diff --git a/engine/cld/legacy/cli/commands/addressbook_test.go b/engine/cld/legacy/cli/commands/addressbook_test.go new file mode 100644 index 00000000..a428b036 --- /dev/null +++ b/engine/cld/legacy/cli/commands/addressbook_test.go @@ -0,0 +1,117 @@ +package commands + +import ( + "strings" + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/domain" +) + +func TestNewAddressBookCmds_Structure(t *testing.T) { + t.Parallel() + c := NewCommands(nil) + var dom domain.Domain + root := c.NewAddressBookCmds(dom) + + require.Equal(t, "address-book", root.Use) + + subs := root.Commands() + require.Len(t, subs, 3, "expected 3 subcommands under 'address-book'") + + uses := make([]string, len(subs)) + for i, sc := range subs { + uses[i] = sc.Use + } + require.ElementsMatch(t, + []string{"merge", "migrate", "remove"}, + uses, + ) + + // The "environment" flag is persistent on root + flag := root.PersistentFlags().Lookup("environment") + require.NotNil(t, flag, "persistent flag 'environment' should exist") +} + +func TestAddressBookCommandMetadata(t *testing.T) { + t.Parallel() + c := NewCommands(nil) + dom := domain.Domain{} + + tests := []struct { + name string + cmdKey string + wantUse string + wantShort string + wantLongPrefix string + wantExampleContains string + wantFlags []string + }{ + { + name: "merge", + cmdKey: "merge", + wantUse: "merge", + wantShort: "Merge the address book", + wantLongPrefix: "Merges the address book artifact", + wantExampleContains: "address-book merge --environment staging --name", + wantFlags: []string{ + "name", "timestamp", + }, + }, + { + name: "migrate", + cmdKey: "migrate", + wantUse: "migrate", + wantShort: "Migrate address book to the new datastore format", + wantLongPrefix: "Converts the address book artifact format", + wantExampleContains: "address-book migrate --environment staging", + wantFlags: []string{}, + }, + { + name: "remove", + cmdKey: "remove", + wantUse: "remove", + wantShort: "Remove changeset address book entries", + wantLongPrefix: "Removes the address book entries", + wantExampleContains: "address-book remove --environment staging --name", + wantFlags: []string{ + "name", "timestamp", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Give each subtest its own fresh command tree + root := c.NewAddressBookCmds(dom) + + t.Parallel() + + parts := strings.Split(tc.cmdKey, " ") + cmd, _, err := root.Find(parts) + require.NoError(t, err) + require.NotNil(t, cmd, "command not found: %s", tc.cmdKey) + + require.Equal(t, tc.wantUse, cmd.Use) + require.Contains(t, cmd.Short, tc.wantShort) + require.Contains(t, cmd.Long, tc.wantLongPrefix) + require.Contains(t, cmd.Example, tc.wantExampleContains) + + for _, flagName := range tc.wantFlags { + var flag *pflag.Flag + if flagName == "environment" { + // persistent flag lives on root + flag = root.PersistentFlags().Lookup("environment") + } else { + flag = cmd.Flags().Lookup(flagName) + if flag == nil { + flag = cmd.PersistentFlags().Lookup(flagName) + } + } + require.NotNil(t, flag, "flag %q not found on %s", flagName, tc.name) + } + }) + } +} From f818be9903267a55de3811e608434445f71e5728 Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Wed, 21 Jan 2026 18:55:02 -0400 Subject: [PATCH 2/8] add top-level datastore CLI commands --- engine/cld/legacy/cli/commands/datastore.go | 210 ++++++++++++++++++ .../cld/legacy/cli/commands/datastore_test.go | 106 +++++++++ 2 files changed, 316 insertions(+) create mode 100644 engine/cld/legacy/cli/commands/datastore.go create mode 100644 engine/cld/legacy/cli/commands/datastore_test.go diff --git a/engine/cld/legacy/cli/commands/datastore.go b/engine/cld/legacy/cli/commands/datastore.go new file mode 100644 index 00000000..eb751d71 --- /dev/null +++ b/engine/cld/legacy/cli/commands/datastore.go @@ -0,0 +1,210 @@ +package commands + +import ( + "fmt" + + "github.com/spf13/cobra" + + cldcatalog "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/catalog" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config" + cfgdomain "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/domain" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/domain" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/legacy/cli" + "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger" +) + +// NewDatastoreCmds creates a new set of commands for datastore operations. +func (c Commands) NewDatastoreCmds(domain domain.Domain) *cobra.Command { + datastoreCmd := &cobra.Command{ + Use: "datastore", + Short: "Datastore operations", + } + + datastoreCmd.AddCommand(c.newDatastoreMerge(domain)) + datastoreCmd.AddCommand(c.newDatastoreSyncToCatalog(domain)) + + datastoreCmd.PersistentFlags().StringP("environment", "e", "", "Deployment environment (required)") + err := datastoreCmd.MarkPersistentFlagRequired("environment") + if err != nil { + return nil + } + + return datastoreCmd +} + +var ( + datastoreMergeLong = cli.LongDesc(` + Merges the datastore artifact of a specific changeset to the main datastore within a + given Domain Environment. The merge destination depends on the datastore configuration: + - file: merges to local JSON files + - catalog: merges to the remote catalog service + - all: merges to both local files and catalog + `) + + datastoreMergeExample = cli.Examples(` + # Merge the datastore for the 0001_deploy_cap changeset in the ccip staging domain + ccip datastore merge --environment staging --name 0001_deploy_cap + + # Merge with a specific durable pipeline timestamp + ccip datastore merge --environment staging --name 0001_deploy_cap --timestamp 1234567890 + `) +) + +// newDatastoreMerge creates a command to merge the datastore for a changeset +func (Commands) newDatastoreMerge(domain domain.Domain) *cobra.Command { + var ( + name string + timestamp string + ) + + cmd := cobra.Command{ + Use: "merge", + Short: "Merge datastore artifacts", + Long: datastoreMergeLong, + Example: datastoreMergeExample, + RunE: func(cmd *cobra.Command, args []string) error { + envKey, _ := cmd.Flags().GetString("environment") + envDir := domain.EnvDir(envKey) + + // Load config to check datastore type + cfg, err := config.Load(domain, envKey, logger.Nop()) + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + + // Determine which merge method to use based on datastore configuration + switch cfg.DatastoreType { + case cfgdomain.DatastoreTypeCatalog: + // Catalog mode - merge to catalog service + cmd.Printf("📡 Using catalog datastore mode (endpoint: %s)\n", cfg.Env.Catalog.GRPC) + + catalog, catalogErr := cldcatalog.LoadCatalog(cmd.Context(), envKey, cfg, domain) + if catalogErr != nil { + return fmt.Errorf("failed to load catalog: %w", catalogErr) + } + + if err := envDir.MergeMigrationDataStoreCatalog(cmd.Context(), name, timestamp, catalog); err != nil { + return fmt.Errorf("error during datastore merge to catalog for %s %s %s: %w", + domain, envKey, name, err, + ) + } + + cmd.Printf("✅ Merged datastore to catalog for %s %s %s\n", + domain, envKey, name, + ) + case cfgdomain.DatastoreTypeFile: + // File mode - merge to local files + cmd.Printf("📁 Using file-based datastore mode\n") + + if err := envDir.MergeMigrationDataStore(name, timestamp); err != nil { + return fmt.Errorf("error during datastore merge to file for %s %s %s: %w", + domain, envKey, name, err, + ) + } + + cmd.Printf("✅ Merged datastore to local files for %s %s %s\n", + domain, envKey, name, + ) + case cfgdomain.DatastoreTypeAll: + // All mode - merge to both catalog and local files + cmd.Printf("📡 Using all datastore mode (catalog: %s, file: %s)\n", cfg.Env.Catalog.GRPC, envDir.DataStoreDirPath()) + + catalog, catalogErr := cldcatalog.LoadCatalog(cmd.Context(), envKey, cfg, domain) + if catalogErr != nil { + return fmt.Errorf("failed to load catalog: %w", catalogErr) + } + + if err := envDir.MergeMigrationDataStoreCatalog(cmd.Context(), name, timestamp, catalog); err != nil { + return fmt.Errorf("error during datastore merge to catalog for %s %s %s: %w", + domain, envKey, name, err, + ) + } + + if err := envDir.MergeMigrationDataStore(name, timestamp); err != nil { + return fmt.Errorf("error during datastore merge to file for %s %s %s: %w", + domain, envKey, name, err, + ) + } + + cmd.Printf("✅ Merged datastore to both catalog and local files for %s %s %s\n", + domain, envKey, name, + ) + default: + return fmt.Errorf("invalid datastore type: %s", cfg.DatastoreType) + } + + return nil + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "name (required)") + cmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "Durable Pipeline timestamp (optional)") + + err := cmd.MarkFlagRequired("name") + if err != nil { + return nil + } + + return &cmd +} + +var ( + datastoreSyncToCatalogLong = cli.LongDesc(` + Syncs the entire local datastore to the catalog service. This is used for initial + migration from file-based to catalog-based datastore management. + + The environment must have catalog configured (datastore type: catalog or all). + `) + + datastoreSyncToCatalogExample = cli.Examples(` + # Sync the entire local datastore to catalog + ccip datastore sync-to-catalog --environment staging + `) +) + +// newDatastoreSyncToCatalog creates a command to sync the entire local datastore to catalog +func (Commands) newDatastoreSyncToCatalog(domain domain.Domain) *cobra.Command { + cmd := cobra.Command{ + Use: "sync-to-catalog", + Short: "Sync local datastore to catalog", + Long: datastoreSyncToCatalogLong, + Example: datastoreSyncToCatalogExample, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + envKey, _ := cmd.Flags().GetString("environment") + envDir := domain.EnvDir(envKey) + + // Load config to get catalog connection details + cfg, err := config.Load(domain, envKey, logger.Nop()) + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + + // Verify catalog is configured + if cfg.DatastoreType != cfgdomain.DatastoreTypeCatalog && cfg.DatastoreType != cfgdomain.DatastoreTypeAll { + return fmt.Errorf("catalog is not configured for environment %s (datastore type: %s)", envKey, cfg.DatastoreType) + } + + cmd.Printf("📡 Syncing local datastore to catalog (endpoint: %s)\n", cfg.Env.Catalog.GRPC) + + catalog, catalogErr := cldcatalog.LoadCatalog(ctx, envKey, cfg, domain) + if catalogErr != nil { + return fmt.Errorf("failed to load catalog: %w", catalogErr) + } + + if err := envDir.SyncDataStoreToCatalog(ctx, catalog); err != nil { + return fmt.Errorf("error syncing datastore to catalog for %s %s: %w", + domain, envKey, err, + ) + } + + cmd.Printf("✅ Successfully synced entire datastore to catalog for %s %s\n", + domain, envKey, + ) + + return nil + }, + } + + return &cmd +} diff --git a/engine/cld/legacy/cli/commands/datastore_test.go b/engine/cld/legacy/cli/commands/datastore_test.go new file mode 100644 index 00000000..eefc8327 --- /dev/null +++ b/engine/cld/legacy/cli/commands/datastore_test.go @@ -0,0 +1,106 @@ +package commands + +import ( + "strings" + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/domain" +) + +func TestNewDatastoreCmds_Structure(t *testing.T) { + t.Parallel() + c := NewCommands(nil) + var dom domain.Domain + root := c.NewDatastoreCmds(dom) + + require.Equal(t, "datastore", root.Use) + + subs := root.Commands() + require.Len(t, subs, 2, "expected 2 subcommands under 'datastore'") + + uses := make([]string, len(subs)) + for i, sc := range subs { + uses[i] = sc.Use + } + require.ElementsMatch(t, + []string{"merge", "sync-to-catalog"}, + uses, + ) + + // The "environment" flag is persistent on root + flag := root.PersistentFlags().Lookup("environment") + require.NotNil(t, flag, "persistent flag 'environment' should exist") +} + +func TestDatastoreCommandMetadata(t *testing.T) { + t.Parallel() + c := NewCommands(nil) + dom := domain.Domain{} + + tests := []struct { + name string + cmdKey string + wantUse string + wantShort string + wantLongPrefix string + wantExampleContains string + wantFlags []string + }{ + { + name: "merge", + cmdKey: "merge", + wantUse: "merge", + wantShort: "Merge datastore artifacts", + wantLongPrefix: "Merges the datastore artifact", + wantExampleContains: "datastore merge --environment staging --name", + wantFlags: []string{ + "name", "timestamp", + }, + }, + { + name: "sync-to-catalog", + cmdKey: "sync-to-catalog", + wantUse: "sync-to-catalog", + wantShort: "Sync local datastore to catalog", + wantLongPrefix: "Syncs the entire local datastore", + wantExampleContains: "datastore sync-to-catalog --environment staging", + wantFlags: []string{}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Give each subtest its own fresh command tree + root := c.NewDatastoreCmds(dom) + + t.Parallel() + + parts := strings.Split(tc.cmdKey, " ") + cmd, _, err := root.Find(parts) + require.NoError(t, err) + require.NotNil(t, cmd, "command not found: %s", tc.cmdKey) + + require.Equal(t, tc.wantUse, cmd.Use) + require.Contains(t, cmd.Short, tc.wantShort) + require.Contains(t, cmd.Long, tc.wantLongPrefix) + require.Contains(t, cmd.Example, tc.wantExampleContains) + + for _, flagName := range tc.wantFlags { + var flag *pflag.Flag + if flagName == "environment" { + // persistent flag lives on root + flag = root.PersistentFlags().Lookup("environment") + } else { + flag = cmd.Flags().Lookup(flagName) + if flag == nil { + flag = cmd.PersistentFlags().Lookup(flagName) + } + } + require.NotNil(t, flag, "flag %q not found on %s", flagName, tc.name) + } + }) + } +} From 7d40135143a50358dfbad3add31358fafd5f8418 Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Wed, 21 Jan 2026 18:57:12 -0400 Subject: [PATCH 3/8] Update engine/cld/legacy/cli/commands/addressbook.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- engine/cld/legacy/cli/commands/addressbook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/cld/legacy/cli/commands/addressbook.go b/engine/cld/legacy/cli/commands/addressbook.go index 8a4104ab..3f3483db 100644 --- a/engine/cld/legacy/cli/commands/addressbook.go +++ b/engine/cld/legacy/cli/commands/addressbook.go @@ -68,7 +68,7 @@ func (Commands) newAddressBookMerge(domain domain.Domain) *cobra.Command { ) } - cmd.Printf("Merged address books for %s %s %s", + cmd.Printf("Merged address books for %s %s %s\n", domain, envKey, name, ) From a2b8c4d468dbd8e125d717501da6eed291b6548e Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Wed, 21 Jan 2026 18:57:19 -0400 Subject: [PATCH 4/8] Update engine/cld/legacy/cli/commands/addressbook.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- engine/cld/legacy/cli/commands/addressbook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/cld/legacy/cli/commands/addressbook.go b/engine/cld/legacy/cli/commands/addressbook.go index 3f3483db..8696e29b 100644 --- a/engine/cld/legacy/cli/commands/addressbook.go +++ b/engine/cld/legacy/cli/commands/addressbook.go @@ -164,7 +164,7 @@ func (Commands) newAddressBookRemove(domain domain.Domain) *cobra.Command { ) } - cmd.Printf("Removed address books for %s %s %s", + cmd.Printf("Removed address books for %s %s %s\n", domain, envKey, name, ) From 175d6fe7747ade1862b9d06802acf376d205e8f4 Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Wed, 21 Jan 2026 18:57:27 -0400 Subject: [PATCH 5/8] Update engine/cld/legacy/cli/commands/addressbook.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- engine/cld/legacy/cli/commands/addressbook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/cld/legacy/cli/commands/addressbook.go b/engine/cld/legacy/cli/commands/addressbook.go index 8696e29b..2af452fa 100644 --- a/engine/cld/legacy/cli/commands/addressbook.go +++ b/engine/cld/legacy/cli/commands/addressbook.go @@ -117,7 +117,7 @@ func (Commands) newAddressBookMigrate(domain domain.Domain) *cobra.Command { ) } - cmd.Printf("Address book for %s %s successfully migrated to the new datastore format", + cmd.Printf("Address book for %s %s successfully migrated to the new datastore format\n", domain, envKey, ) From b9e5d992cbffa72cc5d92c1b618fd7f2fc47ec8c Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Fri, 23 Jan 2026 12:59:30 -0400 Subject: [PATCH 6/8] panic on MarkFlagRequired errors for fail-fast behavior --- engine/cld/legacy/cli/commands/addressbook.go | 17 ++++++----------- engine/cld/legacy/cli/commands/datastore.go | 11 ++++------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/engine/cld/legacy/cli/commands/addressbook.go b/engine/cld/legacy/cli/commands/addressbook.go index 8a4104ab..1df3b9d6 100644 --- a/engine/cld/legacy/cli/commands/addressbook.go +++ b/engine/cld/legacy/cli/commands/addressbook.go @@ -21,9 +21,8 @@ func (c Commands) NewAddressBookCmds(domain domain.Domain) *cobra.Command { addressBookCmd.AddCommand(c.newAddressBookRemove(domain)) addressBookCmd.PersistentFlags().StringP("environment", "e", "", "Deployment environment (required)") - err := addressBookCmd.MarkPersistentFlagRequired("environment") - if err != nil { - return nil + if err := addressBookCmd.MarkPersistentFlagRequired("environment"); err != nil { + panic(fmt.Sprintf("failed to mark environment flag as required: %v", err)) } return addressBookCmd @@ -78,10 +77,8 @@ func (Commands) newAddressBookMerge(domain domain.Domain) *cobra.Command { cmd.Flags().StringVarP(&name, "name", "n", "", "name (required)") cmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "Durable Pipeline timestamp (optional)") - - err := cmd.MarkFlagRequired("name") - if err != nil { - return nil + if err := cmd.MarkFlagRequired("name"); err != nil { + panic(fmt.Sprintf("failed to mark name flag as required: %v", err)) } return &cmd @@ -174,10 +171,8 @@ func (Commands) newAddressBookRemove(domain domain.Domain) *cobra.Command { cmd.Flags().StringVarP(&name, "name", "n", "", "name (required)") cmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "Durable Pipeline timestamp (optional)") - - err := cmd.MarkFlagRequired("name") - if err != nil { - return nil + if err := cmd.MarkFlagRequired("name"); err != nil { + panic(fmt.Sprintf("failed to mark name flag as required: %v", err)) } return &cmd diff --git a/engine/cld/legacy/cli/commands/datastore.go b/engine/cld/legacy/cli/commands/datastore.go index eb751d71..6b7444fe 100644 --- a/engine/cld/legacy/cli/commands/datastore.go +++ b/engine/cld/legacy/cli/commands/datastore.go @@ -24,9 +24,8 @@ func (c Commands) NewDatastoreCmds(domain domain.Domain) *cobra.Command { datastoreCmd.AddCommand(c.newDatastoreSyncToCatalog(domain)) datastoreCmd.PersistentFlags().StringP("environment", "e", "", "Deployment environment (required)") - err := datastoreCmd.MarkPersistentFlagRequired("environment") - if err != nil { - return nil + if err := datastoreCmd.MarkPersistentFlagRequired("environment"); err != nil { + panic(fmt.Sprintf("failed to mark environment flag as required: %v", err)) } return datastoreCmd @@ -139,10 +138,8 @@ func (Commands) newDatastoreMerge(domain domain.Domain) *cobra.Command { cmd.Flags().StringVarP(&name, "name", "n", "", "name (required)") cmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "Durable Pipeline timestamp (optional)") - - err := cmd.MarkFlagRequired("name") - if err != nil { - return nil + if err := cmd.MarkFlagRequired("name"); err != nil { + panic(fmt.Sprintf("failed to mark name flag as required: %v", err)) } return &cmd From 328008e12989b57b1f941414320c87d24708357d Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Sun, 25 Jan 2026 17:28:23 -0400 Subject: [PATCH 7/8] move parallel call up --- engine/cld/legacy/cli/commands/addressbook_test.go | 4 ++-- engine/cld/legacy/cli/commands/datastore_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/cld/legacy/cli/commands/addressbook_test.go b/engine/cld/legacy/cli/commands/addressbook_test.go index a428b036..295ce4c6 100644 --- a/engine/cld/legacy/cli/commands/addressbook_test.go +++ b/engine/cld/legacy/cli/commands/addressbook_test.go @@ -84,11 +84,11 @@ func TestAddressBookCommandMetadata(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // Give each subtest its own fresh command tree root := c.NewAddressBookCmds(dom) - t.Parallel() - parts := strings.Split(tc.cmdKey, " ") cmd, _, err := root.Find(parts) require.NoError(t, err) diff --git a/engine/cld/legacy/cli/commands/datastore_test.go b/engine/cld/legacy/cli/commands/datastore_test.go index eefc8327..1313fe9d 100644 --- a/engine/cld/legacy/cli/commands/datastore_test.go +++ b/engine/cld/legacy/cli/commands/datastore_test.go @@ -73,11 +73,11 @@ func TestDatastoreCommandMetadata(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // Give each subtest its own fresh command tree root := c.NewDatastoreCmds(dom) - t.Parallel() - parts := strings.Split(tc.cmdKey, " ") cmd, _, err := root.Find(parts) require.NoError(t, err) From 85bf13cedac858ec1626a8c7f643b4f9a80e0d42 Mon Sep 17 00:00:00 2001 From: Damien Hobday Date: Sun, 25 Jan 2026 17:31:35 -0400 Subject: [PATCH 8/8] cs --- .changeset/tangy-jokes-jam.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tangy-jokes-jam.md diff --git a/.changeset/tangy-jokes-jam.md b/.changeset/tangy-jokes-jam.md new file mode 100644 index 00000000..a631f895 --- /dev/null +++ b/.changeset/tangy-jokes-jam.md @@ -0,0 +1,5 @@ +--- +"chainlink-deployments-framework": minor +--- + +Add top-level address-book and datastore CLI commands