diff --git a/deployment/changeset.go b/deployment/changeset.go index 2a571b48..a57377f1 100644 --- a/deployment/changeset.go +++ b/deployment/changeset.go @@ -125,7 +125,46 @@ type ChangesetOutput struct { // the on and offchain state of the environment. type ViewState func(e Environment) (json.Marshaler, error) -type ViewStateV2 func(e Environment, previousView json.Marshaler) (json.Marshaler, error) +// ViewStateConfig contains configuration options for ViewStateV2. +type ViewStateConfig struct { + // ChainSelectorsToLoad specifies which chains to load/update in the state snapshot. + // If nil or empty, all chains in the environment should be included. + ChainSelectorsToLoad []uint64 +} + +// ViewStateOption is a functional option for configuring ViewStateV2. +type ViewStateOption func(*ViewStateConfig) + +// WithChainSelectorsToLoad sets the chain selectors to load/update in the state snapshot. +// +// Example usage: +// +// state, err := viewStateFunc(env, prevState, deployment.WithChainSelectorsToLoad([]uint64{1, 2, 3})) +func WithChainSelectorsToLoad(chainSelectorsToUpdate []uint64) ViewStateOption { + return func(cfg *ViewStateConfig) { + cfg.ChainSelectorsToLoad = chainSelectorsToUpdate + } +} + +// ViewStateV2 produces a product specific JSON representation of the on and offchain +// state of the environment, with optional configuration via functional options. +// +// The function receives: +// - e: The environment to snapshot +// - previousView: The previous state (if any) for incremental updates +// - opts: Optional configuration options (e.g., ChainSelectorsToLoad) +// +// To parse options in your implementation: +// +// func MyViewState(e Environment, previousView json.Marshaler, opts ...ViewStateOption) (json.Marshaler, error) { +// cfg := &ViewStateConfig{} +// for _, opt := range opts { +// opt(cfg) +// } +// // Use cfg.ChainSelectorsToLoad to determine which chains to load +// // ... +// } +type ViewStateV2 func(e Environment, previousView json.Marshaler, opts ...ViewStateOption) (json.Marshaler, error) // MergeChangesetOutput merges the source ChangesetOutput into the destination ChangesetOutput. // It is useful to combine multiple ChangesetOutput objects into one to create one consolidated changeset from multiple granular changesets. diff --git a/engine/cld/legacy/cli/commands/state.go b/engine/cld/legacy/cli/commands/state.go index 82e7949b..cd017a8e 100644 --- a/engine/cld/legacy/cli/commands/state.go +++ b/engine/cld/legacy/cli/commands/state.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "strconv" + "strings" "time" "github.com/spf13/cobra" @@ -37,6 +39,7 @@ func (c Commands) newStateGenerate(dom domain.Domain, cfg StateConfig) *cobra.Co persist bool outputPath string prevStatePath string + chainsStr string ) cmd := cobra.Command{ @@ -52,7 +55,35 @@ func (c Commands) newStateGenerate(dom domain.Domain, cfg StateConfig) *cobra.Co ctx, cancel := context.WithTimeout(cmd.Context(), viewTimeout) defer cancel() - env, err := environment.Load(ctx, dom, envKey, environment.WithLogger(c.lggr)) + // Parse chain selectors from the comma-separated string + var chains []uint64 + if chainsStr != "" { + chainParts := strings.Split(chainsStr, ",") + for _, part := range chainParts { + part = strings.TrimSpace(part) + if part == "" { + continue + } + selector, parseErr := strconv.ParseUint(part, 10, 64) + if parseErr != nil { + return fmt.Errorf("invalid chain selector '%s': %w", part, parseErr) + } + chains = append(chains, selector) + } + } + + // Prepare environment load options + envOpts := []environment.LoadEnvironmentOption{environment.WithLogger(c.lggr)} + + // If specific chains are requested, only load those chains + if len(chains) > 0 { + cmd.Printf("Loading state for specific chains: %v\n", chains) + envOpts = append(envOpts, environment.OnlyLoadChainsFor(chains)) + } else { + cmd.Println("Loading state for all chains") + } + + env, err := environment.Load(ctx, dom, envKey, envOpts...) if err != nil { return fmt.Errorf("failed to load environment %w", err) } @@ -62,7 +93,13 @@ func (c Commands) newStateGenerate(dom domain.Domain, cfg StateConfig) *cobra.Co return fmt.Errorf("failed to load previous state: %w", err) } - state, err := cfg.ViewState(env, prevState) + // Generate state using ViewStateV2 with optional chain selectors + var opts []deployment.ViewStateOption + if len(chains) > 0 { + opts = append(opts, deployment.WithChainSelectorsToLoad(chains)) + } + + state, err := cfg.ViewState(env, prevState, opts...) if err != nil { return fmt.Errorf("unable to snapshot state: %w", err) } @@ -95,6 +132,7 @@ func (c Commands) newStateGenerate(dom domain.Domain, cfg StateConfig) *cobra.Co cmd.Flags().BoolVarP(&persist, "persist", "p", false, "Persist state to disk") cmd.Flags().StringVarP(&outputPath, "outputPath", "o", "", "Output path. Default is //state.json") cmd.Flags().StringVarP(&prevStatePath, "previousState", "s", "", "Previous state's path. Default is //state.json") + cmd.Flags().StringVarP(&chainsStr, "chains", "c", "", "Chain selectors to fetch state for (comma-separated). If not specified, all chains will be loaded") return &cmd } diff --git a/engine/cld/legacy/cli/commands/state_test.go b/engine/cld/legacy/cli/commands/state_test.go index 302766cb..6464c5f4 100644 --- a/engine/cld/legacy/cli/commands/state_test.go +++ b/engine/cld/legacy/cli/commands/state_test.go @@ -54,6 +54,11 @@ func TestNewStateGenerateCmd_Metadata(t *testing.T) { require.NotNil(t, s) require.Equal(t, "s", s.Shorthand) require.Empty(t, s.Value.String()) + + c := cmd.Flags().Lookup("chains") + require.NotNil(t, c) + require.Equal(t, "c", c.Shorthand) + require.Empty(t, c.Value.String()) } func TestStateGenerate_MissingEnvFails(t *testing.T) {