From 776ea10dba4d1e14c395e216caaa5e2045f25faa Mon Sep 17 00:00:00 2001 From: aryansharma9917 Date: Wed, 11 Feb 2026 11:01:43 +0530 Subject: [PATCH 1/4] Add deployment command builder for strategy-based execution --- pkg/deploy/builder.go | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 pkg/deploy/builder.go diff --git a/pkg/deploy/builder.go b/pkg/deploy/builder.go new file mode 100644 index 0000000..29ec5ee --- /dev/null +++ b/pkg/deploy/builder.go @@ -0,0 +1,62 @@ +package deploy + +import ( + "fmt" + + "github.com/aryansharma9917/codewise-cli/pkg/env" +) + +type Command struct { + Name string + Args []string +} + +func BuildCommand(environment *env.Env) (*Command, Strategy, error) { + + strategy := ResolveStrategy(environment) + + switch strategy { + + case StrategyHelm: + + args := []string{ + "upgrade", + "--install", + environment.Helm.Release, + environment.Helm.Chart, + "--namespace", + environment.K8s.Namespace, + } + + if environment.K8s.Context != "" { + args = append(args, "--kube-context", environment.K8s.Context) + } + + return &Command{ + Name: "helm", + Args: args, + }, strategy, nil + + case StrategyKubectl: + + args := []string{ + "apply", + "-f", + "k8s/", + "-n", + environment.K8s.Namespace, + } + + if environment.K8s.Context != "" { + args = append(args, "--context", environment.K8s.Context) + } + + return &Command{ + Name: "kubectl", + Args: args, + }, strategy, nil + + default: + return nil, "", fmt.Errorf("unknown deployment strategy") + } +} From 7953121bf1ea4c9c74aa1db56a3af9ba7bb85396 Mon Sep 17 00:00:00 2001 From: aryansharma9917 Date: Wed, 11 Feb 2026 11:01:47 +0530 Subject: [PATCH 2/4] Add deployment planning command for execution preview --- cmd/deploy.go | 32 +++++++++++++++++++++++++++++--- pkg/deploy/plan.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 pkg/deploy/plan.go diff --git a/cmd/deploy.go b/cmd/deploy.go index 621c68a..2cfe22c 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -14,7 +14,12 @@ var ( var deployCmd = &cobra.Command{ Use: "deploy", - Short: "Deploy an application using the configured environment", + Short: "Deployment operations", +} + +var deployRunCmd = &cobra.Command{ + Use: "run", + Short: "Execute deployment", Run: func(cmd *cobra.Command, args []string) { if deployEnv == "" { @@ -28,10 +33,31 @@ var deployCmd = &cobra.Command{ }, } +var deployPlanCmd = &cobra.Command{ + Use: "plan", + Short: "Preview deployment execution plan", + Run: func(cmd *cobra.Command, args []string) { + + if deployEnv == "" { + fmt.Println("please provide --env") + return + } + + if err := deploy.Plan(deployEnv); err != nil { + fmt.Println("plan error:", err) + } + }, +} + func init() { - deployCmd.Flags().StringVar(&deployEnv, "env", "", "Environment to deploy") - deployCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Preview deployment") + deployRunCmd.Flags().StringVar(&deployEnv, "env", "", "Environment to deploy") + deployRunCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Preview deployment") + + deployPlanCmd.Flags().StringVar(&deployEnv, "env", "", "Environment to plan") + + deployCmd.AddCommand(deployRunCmd) + deployCmd.AddCommand(deployPlanCmd) rootCmd.AddCommand(deployCmd) } diff --git a/pkg/deploy/plan.go b/pkg/deploy/plan.go new file mode 100644 index 0000000..36c949b --- /dev/null +++ b/pkg/deploy/plan.go @@ -0,0 +1,33 @@ +package deploy + +import ( + "fmt" + "strings" +) + +func Plan(envName string) error { + + environment, err := LoadEnvironment(envName) + if err != nil { + return err + } + + command, strategy, err := BuildCommand(environment) + if err != nil { + return err + } + + fmt.Println("\nDeployment Plan") + fmt.Println("---------------") + + fmt.Println("Environment:", envName) + fmt.Println("Strategy:", strategy) + + fmt.Println("\nCommand:") + fmt.Printf("%s %s\n\n", + command.Name, + strings.Join(command.Args, " "), + ) + + return nil +} From 5335ab1980f8d375bee06cc0e01c18c83a974608 Mon Sep 17 00:00:00 2001 From: aryansharma9917 Date: Wed, 11 Feb 2026 11:01:52 +0530 Subject: [PATCH 3/4] Refactor deploy engine to use command builder and enforce environment validation --- pkg/deploy/deploy.go | 57 +++++++------------------------------------- pkg/deploy/loader.go | 7 ++++-- 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index 2d519bb..aaef95c 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -22,57 +22,18 @@ func Run(envName string, dryRun bool) error { return err } - strategy := ResolveStrategy(environment) + command, _, err := BuildCommand(environment) + if err != nil { + return err + } + + if err := checkDependency(command.Name); err != nil { + return err + } executor := Executor{ DryRun: dryRun, } - switch strategy { - - case StrategyHelm: - - if err := checkDependency("helm"); err != nil { - return err - } - - args := []string{ - "upgrade", - "--install", - environment.Helm.Release, - environment.Helm.Chart, - "--namespace", - environment.K8s.Namespace, - } - - // inject kube-context if provided - if environment.K8s.Context != "" { - args = append(args, "--kube-context", environment.K8s.Context) - } - - return executor.Run("helm", args...) - - case StrategyKubectl: - - if err := checkDependency("kubectl"); err != nil { - return err - } - - args := []string{ - "apply", - "-f", - "k8s/", - "-n", - environment.K8s.Namespace, - } - - if environment.K8s.Context != "" { - args = append(args, "--context", environment.K8s.Context) - } - - return executor.Run("kubectl", args...) - - default: - return fmt.Errorf("unknown deployment strategy") - } + return executor.Run(command.Name, command.Args...) } diff --git a/pkg/deploy/loader.go b/pkg/deploy/loader.go index 044eeb4..25db124 100644 --- a/pkg/deploy/loader.go +++ b/pkg/deploy/loader.go @@ -9,8 +9,11 @@ import ( func LoadEnvironment(name string) (*env.Env, error) { e, err := env.LoadEnv(name) - if err != nil { - return nil, fmt.Errorf("failed to load environment: %w", err) + if err != nil || e == nil { + return nil, fmt.Errorf( + "environment %q not found. run 'codewise env list' to see available environments", + name, + ) } return e, nil From 0afe719a13d35de40634d8c39c70eac8ad410076 Mon Sep 17 00:00:00 2001 From: aryansharma9917 Date: Wed, 11 Feb 2026 11:01:55 +0530 Subject: [PATCH 4/4] Enforce strict environment loading and remove duplicate loader --- pkg/env/env.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- pkg/env/list.go | 64 ++++++++++++++---------------------------- 2 files changed, 93 insertions(+), 45 deletions(-) diff --git a/pkg/env/env.go b/pkg/env/env.go index fbcb48e..9244ab3 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -4,19 +4,24 @@ import ( "fmt" "os" "path/filepath" + + "gopkg.in/yaml.v3" ) const ( codewiseHomeEnv = "CODEWISE_HOME" ) +///////////////////////////////////////////////////////// +// PATH HELPERS +///////////////////////////////////////////////////////// + func baseEnvPath() (string, error) { - // Check override via env var + if home := os.Getenv(codewiseHomeEnv); home != "" { return filepath.Join(home, "envs"), nil } - // Fallback to ~/.codewise/envs home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("failed to resolve user home: %w", err) @@ -26,26 +31,91 @@ func baseEnvPath() (string, error) { } func envDir(name string) (string, error) { + base, err := baseEnvPath() if err != nil { return "", err } + return filepath.Join(base, name), nil } func envExists(name string) bool { + dir, err := envDir(name) if err != nil { return false } + info, err := os.Stat(dir) return err == nil && info.IsDir() } func ensureBaseDir() error { + base, err := baseEnvPath() if err != nil { return err } + return os.MkdirAll(base, 0755) } + +///////////////////////////////////////////////////////// +// YAML READER +///////////////////////////////////////////////////////// + +func readYAML(path string, out interface{}) error { + + raw, err := os.ReadFile(path) + if err != nil { + return err + } + + return yaml.Unmarshal(raw, out) +} + +///////////////////////////////////////////////////////// +// SINGLE SOURCE OF TRUTH +///////////////////////////////////////////////////////// + +func LoadEnv(name string) (*Env, error) { + + if !envExists(name) { + return nil, fmt.Errorf("environment %q does not exist", name) + } + + dir, err := envDir(name) + if err != nil { + return nil, err + } + + k8s := K8sConfig{} + helm := HelmConfig{} + gitops := GitOpsConfig{} + values := ValuesConfig{} + + if err := readYAML(filepath.Join(dir, "k8s.yaml"), &k8s); err != nil { + return nil, err + } + + if err := readYAML(filepath.Join(dir, "helm.yaml"), &helm); err != nil { + return nil, err + } + + if err := readYAML(filepath.Join(dir, "gitops.yaml"), &gitops); err != nil { + return nil, err + } + + if err := readYAML(filepath.Join(dir, "values.yaml"), &values); err != nil { + return nil, err + } + + return &Env{ + Name: name, + K8s: k8s, + Helm: helm, + GitOps: gitops, + Values: values, + }, nil +} diff --git a/pkg/env/list.go b/pkg/env/list.go index 2f26136..cbf5ecd 100644 --- a/pkg/env/list.go +++ b/pkg/env/list.go @@ -2,67 +2,45 @@ package env import ( "os" - "path/filepath" - - "gopkg.in/yaml.v3" ) +// ListEnvs returns all available environments func ListEnvs() ([]Env, error) { + base, err := baseEnvPath() if err != nil { return nil, err } + // If no environments exist yet, + // return empty slice instead of error. + if _, err := os.Stat(base); os.IsNotExist(err) { + return []Env{}, nil + } + entries, err := os.ReadDir(base) if err != nil { return nil, err } var envs []Env - for _, entry := range entries { - if entry.IsDir() { - name := entry.Name() - e, err := LoadEnv(name) - if err != nil { - continue - } - envs = append(envs, *e) - } - } - - return envs, nil -} -func LoadEnv(name string) (*Env, error) { - dir, err := envDir(name) - if err != nil { - return nil, err - } + for _, entry := range entries { - k8s := K8sConfig{} - helm := HelmConfig{} - gitops := GitOpsConfig{} - values := ValuesConfig{} + if !entry.IsDir() { + continue + } - // read each file if exists - _ = readYAML(filepath.Join(dir, "k8s.yaml"), &k8s) - _ = readYAML(filepath.Join(dir, "helm.yaml"), &helm) - _ = readYAML(filepath.Join(dir, "gitops.yaml"), &gitops) - _ = readYAML(filepath.Join(dir, "values.yaml"), &values) + name := entry.Name() - return &Env{ - Name: name, - K8s: k8s, - Helm: helm, - GitOps: gitops, - Values: values, - }, nil -} + e, err := LoadEnv(name) + if err != nil { + // skip corrupted envs safely + continue + } -func readYAML(path string, out interface{}) error { - raw, err := os.ReadFile(path) - if err != nil { - return err + envs = append(envs, *e) } - return yaml.Unmarshal(raw, out) + + return envs, nil }