diff --git a/cmd/pro/check_update.go b/cmd/pro/check_update.go index b1c01e57f..290ebb63d 100644 --- a/cmd/pro/check_update.go +++ b/cmd/pro/check_update.go @@ -25,6 +25,8 @@ type CheckUpdateCmd struct { } // NewCheckUpdateCmd creates a new command. +// +//nolint:dupl // structurally similar to NewHealthCmd; intentional sibling factory func NewCheckUpdateCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &CheckUpdateCmd{ GlobalFlags: globalFlags, diff --git a/cmd/pro/daemon/netcheck.go b/cmd/pro/daemon/netcheck.go index ee9e39dd6..ea593fb07 100644 --- a/cmd/pro/daemon/netcheck.go +++ b/cmd/pro/daemon/netcheck.go @@ -27,6 +27,8 @@ type NetcheckCmd struct { } // NewNetcheckCmd creates a new command. +// +//nolint:dupl // structurally similar to NewStatusCmd; intentional sibling factory func NewNetcheckCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &NetcheckCmd{ GlobalFlags: flags, diff --git a/cmd/pro/daemon/status.go b/cmd/pro/daemon/status.go index 84be6a2f5..c23f272f4 100644 --- a/cmd/pro/daemon/status.go +++ b/cmd/pro/daemon/status.go @@ -24,6 +24,8 @@ type StatusCmd struct { } // NewStatusCmd creates a new command. +// +//nolint:dupl // structurally similar to NewNetcheckCmd; intentional sibling factory func NewStatusCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &StatusCmd{ GlobalFlags: flags, diff --git a/cmd/pro/provider/exec.go b/cmd/pro/provider/exec.go new file mode 100644 index 000000000..3da7ded54 --- /dev/null +++ b/cmd/pro/provider/exec.go @@ -0,0 +1,54 @@ +package provider + +import ( + "context" + "fmt" + "io" + "net/url" + + "github.com/devsy-org/devsy/pkg/platform" + "github.com/devsy-org/devsy/pkg/platform/client" + "github.com/devsy-org/devsy/pkg/platform/remotecommand" +) + +type dialAndExecuteParams struct { + configPath string + action string + envFlags url.Values + stdin io.Reader + stdout io.Writer + stderr io.Writer +} + +// dialAndExecute finds the current workspace, dials the given sub-resource +// action on it, and streams the resulting connection through stdin/stdout/stderr. +func dialAndExecute(ctx context.Context, params dialAndExecuteParams) error { + baseClient, err := client.InitClientFromPath(ctx, params.configPath) + if err != nil { + return err + } + + info, err := platform.GetWorkspaceInfoFromEnv() + if err != nil { + return err + } + opts := platform.FindInstanceOptions{UID: info.UID, ProjectName: info.ProjectName} + workspace, err := platform.FindInstance(ctx, baseClient, opts) + if err != nil { + return err + } else if workspace == nil { + return fmt.Errorf("couldn't find workspace") + } + + conn, err := platform.DialInstance(baseClient, workspace, params.action, params.envFlags) + if err != nil { + return err + } + + _, err = remotecommand.ExecuteConn(ctx, conn, params.stdin, params.stdout, params.stderr) + if err != nil { + return fmt.Errorf("error executing: %w", err) + } + + return nil +} diff --git a/cmd/pro/provider/ssh.go b/cmd/pro/provider/ssh.go index 976789751..371212b26 100644 --- a/cmd/pro/provider/ssh.go +++ b/cmd/pro/provider/ssh.go @@ -1,16 +1,14 @@ +//nolint:dupl // structurally similar to stop.go; intentional sibling command sharing dialAndExecute package provider import ( "context" - "fmt" "io" "os" "github.com/devsy-org/devsy/cmd/pro/flags" "github.com/devsy-org/devsy/pkg/config" "github.com/devsy-org/devsy/pkg/platform" - "github.com/devsy-org/devsy/pkg/platform/client" - "github.com/devsy-org/devsy/pkg/platform/remotecommand" "github.com/spf13/cobra" ) @@ -43,43 +41,12 @@ func (cmd *SshCmd) Run( stdout io.Writer, stderr io.Writer, ) error { - baseClient, err := client.InitClientFromPath(ctx, cmd.Config) - if err != nil { - return err - } - - info, err := platform.GetWorkspaceInfoFromEnv() - if err != nil { - return err - } - opts := platform.FindInstanceOptions{UID: info.UID, ProjectName: info.ProjectName} - workspace, err := platform.FindInstance(ctx, baseClient, opts) - if err != nil { - return err - } else if workspace == nil { - return fmt.Errorf("couldn't find workspace") - } - - conn, err := platform.DialInstance( - baseClient, - workspace, - "ssh", - platform.OptionsFromEnv(config.EnvFlagsSSH), - ) - if err != nil { - return err - } - - _, err = remotecommand.ExecuteConn( - ctx, - conn, - stdin, - stdout, - stderr, - ) - if err != nil { - return fmt.Errorf("error executing: %w", err) - } - - return nil + return dialAndExecute(ctx, dialAndExecuteParams{ + configPath: cmd.Config, + action: "ssh", + envFlags: platform.OptionsFromEnv(config.EnvFlagsSSH), + stdin: stdin, + stdout: stdout, + stderr: stderr, + }) } diff --git a/cmd/pro/provider/stop.go b/cmd/pro/provider/stop.go index a811d49b5..0f93a631a 100644 --- a/cmd/pro/provider/stop.go +++ b/cmd/pro/provider/stop.go @@ -1,16 +1,14 @@ +//nolint:dupl // structurally similar to ssh.go; intentional sibling command sharing dialAndExecute package provider import ( "context" - "fmt" "io" "os" storagev1 "github.com/devsy-org/api/pkg/apis/storage/v1" "github.com/devsy-org/devsy/cmd/pro/flags" "github.com/devsy-org/devsy/pkg/platform" - "github.com/devsy-org/devsy/pkg/platform/client" - "github.com/devsy-org/devsy/pkg/platform/remotecommand" "github.com/spf13/cobra" ) @@ -43,43 +41,12 @@ func (cmd *StopCmd) Run( stdout io.Writer, stderr io.Writer, ) error { - baseClient, err := client.InitClientFromPath(ctx, cmd.Config) - if err != nil { - return err - } - - info, err := platform.GetWorkspaceInfoFromEnv() - if err != nil { - return err - } - opts := platform.FindInstanceOptions{UID: info.UID, ProjectName: info.ProjectName} - workspace, err := platform.FindInstance(ctx, baseClient, opts) - if err != nil { - return err - } else if workspace == nil { - return fmt.Errorf("couldn't find workspace") - } - - conn, err := platform.DialInstance( - baseClient, - workspace, - "stop", - platform.OptionsFromEnv(storagev1.DevsyFlagsStop), - ) - if err != nil { - return err - } - - _, err = remotecommand.ExecuteConn( - ctx, - conn, - stdin, - stdout, - stderr, - ) - if err != nil { - return fmt.Errorf("error executing: %w", err) - } - - return nil + return dialAndExecute(ctx, dialAndExecuteParams{ + configPath: cmd.Config, + action: "stop", + envFlags: platform.OptionsFromEnv(storagev1.DevsyFlagsStop), + stdin: stdin, + stdout: stdout, + stderr: stderr, + }) } diff --git a/pkg/options/resolver/resolver_test.go b/pkg/options/resolver/resolver_test.go index 122de9bf1..a196766ff 100644 --- a/pkg/options/resolver/resolver_test.go +++ b/pkg/options/resolver/resolver_test.go @@ -215,14 +215,7 @@ func (suite *ResolverTestSuite) TestResolveOptions_ExpiredCache() { } func (suite *ResolverTestSuite) TestResolveOptions_PreserveChildWhenParentUnchanged() { - suite.resolver.graph = graph.NewGraph[*types.Option]() - - parentOption := &types.Option{Description: "Parent", Default: "new_parent_value"} - childOption := &types.Option{Description: "Child", Default: "child_default"} - - suite.Require().NoError(suite.resolver.graph.AddNode("parent", parentOption)) - suite.Require().NoError(suite.resolver.graph.AddNode("child", childOption)) - suite.Require().NoError(suite.resolver.graph.AddEdge("parent", "child")) + suite.setupParentChildGraph() existingValues := map[string]config.OptionValue{ "parent": {Value: "old_parent_value", UserProvided: true}, @@ -237,14 +230,7 @@ func (suite *ResolverTestSuite) TestResolveOptions_PreserveChildWhenParentUnchan } func (suite *ResolverTestSuite) TestResolveOptions_PreserveUserProvidedChild() { - suite.resolver.graph = graph.NewGraph[*types.Option]() - - parentOption := &types.Option{Description: "Parent", Default: "new_parent_value"} - childOption := &types.Option{Description: "Child", Default: "child_default"} - - suite.Require().NoError(suite.resolver.graph.AddNode("parent", parentOption)) - suite.Require().NoError(suite.resolver.graph.AddNode("child", childOption)) - suite.Require().NoError(suite.resolver.graph.AddEdge("parent", "child")) + suite.setupParentChildGraph() existingValues := map[string]config.OptionValue{ "parent": {Value: "old_parent_value", UserProvided: true}, @@ -321,3 +307,17 @@ func (suite *ResolverTestSuite) TestAddOptionsToGraph_MultipleCalls() { nodes := g.GetNodes() suite.Len(nodes, 2, "Multiple calls to addOptionsToGraph should not duplicate nodes.") } + +// setupParentChildGraph creates a resolver graph with a "parent" option that +// has a "child" option depending on it, used by tests that verify +// user-provided-value precedence during resolution. +func (suite *ResolverTestSuite) setupParentChildGraph() { + suite.resolver.graph = graph.NewGraph[*types.Option]() + + parentOption := &types.Option{Description: "Parent", Default: "new_parent_value"} + childOption := &types.Option{Description: "Child", Default: "child_default"} + + suite.Require().NoError(suite.resolver.graph.AddNode("parent", parentOption)) + suite.Require().NoError(suite.resolver.graph.AddNode("child", childOption)) + suite.Require().NoError(suite.resolver.graph.AddEdge("parent", "child")) +}