Skip to content

Commit da40706

Browse files
Add environment subsystem skeleton with create command
1 parent 4c8e5a1 commit da40706

9 files changed

Lines changed: 503 additions & 0 deletions

File tree

cmd/env.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cmd
2+
3+
import "github.com/spf13/cobra"
4+
5+
var envCmd = &cobra.Command{
6+
Use: "env",
7+
Short: "Manage deployment environments",
8+
Long: "Create, list, and delete Codewise deployment environments.",
9+
}
10+
11+
func init() {
12+
rootCmd.AddCommand(envCmd)
13+
}

cmd/env_create.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
survey "github.com/AlecAivazis/survey/v2"
7+
"github.com/aryansharma9917/codewise-cli/pkg/config"
8+
"github.com/aryansharma9917/codewise-cli/pkg/env"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var interactive bool
13+
14+
var envCreateCmd = &cobra.Command{
15+
Use: "create <name>",
16+
Short: "Create a new environment",
17+
Args: cobra.ExactArgs(1),
18+
Run: func(cmd *cobra.Command, args []string) {
19+
name := args[0]
20+
21+
if interactive {
22+
if err := createEnvInteractive(name); err != nil {
23+
fmt.Println("error:", err)
24+
return
25+
}
26+
fmt.Println("environment", name, "created")
27+
return
28+
}
29+
30+
if err := env.CreateEnv(name, env.CreateOptions{}); err != nil {
31+
fmt.Println("error:", err)
32+
return
33+
}
34+
fmt.Println("environment", name, "created")
35+
},
36+
}
37+
38+
func init() {
39+
envCreateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Enable interactive mode")
40+
envCmd.AddCommand(envCreateCmd)
41+
}
42+
43+
func createEnvInteractive(name string) error {
44+
cfg, _ := config.ReadConfig()
45+
46+
defaultNs := fallback(cfg.Defaults.Namespace, name)
47+
defaultCtx := fallback(cfg.Defaults.Context, "")
48+
defaultRepo := fallback(cfg.Defaults.RepoURL, "")
49+
defaultBranch := fallback(cfg.Defaults.Branch, "main")
50+
defaultImage := fallback(cfg.Defaults.Image, "codewise")
51+
defaultTag := fallback(cfg.Defaults.ImageTag, "latest")
52+
53+
answers := struct {
54+
Namespace string
55+
Context string
56+
Repo string
57+
Branch string
58+
Image string
59+
Tag string
60+
}{}
61+
62+
qs := []*survey.Question{
63+
{Name: "Namespace", Prompt: &survey.Input{Message: fmt.Sprintf("Namespace (default: %s)", defaultNs)}},
64+
{Name: "Context", Prompt: &survey.Input{Message: fmt.Sprintf("Kubernetes context (default: %s)", defaultCtx)}},
65+
{Name: "Repo", Prompt: &survey.Input{Message: fmt.Sprintf("GitOps repo (default: %s)", defaultRepo)}},
66+
{Name: "Branch", Prompt: &survey.Input{Message: fmt.Sprintf("GitOps branch (default: %s)", defaultBranch)}},
67+
{Name: "Image", Prompt: &survey.Input{Message: fmt.Sprintf("Image repository (default: %s)", defaultImage)}},
68+
{Name: "Tag", Prompt: &survey.Input{Message: fmt.Sprintf("Image tag (default: %s)", defaultTag)}},
69+
}
70+
71+
if err := survey.Ask(qs, &answers); err != nil {
72+
return err
73+
}
74+
75+
k8s := env.K8sConfig{
76+
Namespace: fallback(answers.Namespace, defaultNs),
77+
Context: fallback(answers.Context, defaultCtx),
78+
}
79+
80+
helm := env.HelmConfig{
81+
Release: name,
82+
Chart: "./helm/chart",
83+
Values: "./values.yaml",
84+
}
85+
86+
gitops := env.GitOpsConfig{
87+
Repo: fallback(answers.Repo, defaultRepo),
88+
Path: "",
89+
Branch: fallback(answers.Branch, defaultBranch),
90+
}
91+
92+
values := env.ValuesConfig{}
93+
values.Image.Repository = fallback(answers.Image, defaultImage)
94+
values.Image.Tag = fallback(answers.Tag, defaultTag)
95+
96+
return env.CreateEnvFromParts(name, k8s, helm, gitops, values)
97+
}
98+
99+
func fallback(input, def string) string {
100+
if input != "" {
101+
return input
102+
}
103+
return def
104+
}

cmd/env_delete.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
survey "github.com/AlecAivazis/survey/v2"
7+
"github.com/aryansharma9917/codewise-cli/pkg/env"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var yes bool
12+
13+
var envDeleteCmd = &cobra.Command{
14+
Use: "delete <name>",
15+
Short: "Delete an environment",
16+
Args: cobra.ExactArgs(1),
17+
Run: func(cmd *cobra.Command, args []string) {
18+
name := args[0]
19+
20+
if !yes {
21+
var confirm bool
22+
prompt := &survey.Confirm{
23+
Message: fmt.Sprintf("Delete environment %q?", name),
24+
}
25+
if err := survey.AskOne(prompt, &confirm); err != nil {
26+
fmt.Println("error:", err)
27+
return
28+
}
29+
if !confirm {
30+
fmt.Println("aborted")
31+
return
32+
}
33+
}
34+
35+
if err := env.DeleteEnv(name, env.DeleteOptions{Force: yes}); err != nil {
36+
fmt.Println("error:", err)
37+
return
38+
}
39+
40+
fmt.Println("environment", name, "deleted")
41+
},
42+
}
43+
44+
func init() {
45+
envDeleteCmd.Flags().BoolVar(&yes, "yes", false, "Skip confirmation")
46+
envCmd.AddCommand(envDeleteCmd)
47+
}

cmd/env_list.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/aryansharma9917/codewise-cli/pkg/env"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var envListCmd = &cobra.Command{
11+
Use: "list",
12+
Short: "List environments",
13+
Run: func(cmd *cobra.Command, args []string) {
14+
envs, err := env.ListEnvs()
15+
if err != nil {
16+
fmt.Println("error:", err)
17+
return
18+
}
19+
20+
if len(envs) == 0 {
21+
fmt.Println("no environments found")
22+
return
23+
}
24+
25+
for _, e := range envs {
26+
fmt.Printf("%-10s namespace=%s context=%s\n",
27+
e.Name, e.K8s.Namespace, e.K8s.Context)
28+
}
29+
},
30+
}
31+
32+
func init() {
33+
envCmd.AddCommand(envListCmd)
34+
}

pkg/env/create.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/aryansharma9917/codewise-cli/pkg/config"
9+
"gopkg.in/yaml.v3"
10+
)
11+
12+
type CreateOptions struct {
13+
Interactive bool
14+
}
15+
16+
func CreateEnv(name string, opts CreateOptions) error {
17+
if opts.Interactive {
18+
// handled at CLI layer
19+
return fmt.Errorf("interactive mode not implemented in CreateEnv")
20+
}
21+
return createSilent(name)
22+
}
23+
24+
func createSilent(name string) error {
25+
if err := ensureBaseDir(); err != nil {
26+
return err
27+
}
28+
29+
if envExists(name) {
30+
return fmt.Errorf("environment %q already exists", name)
31+
}
32+
33+
dir, err := envDir(name)
34+
if err != nil {
35+
return err
36+
}
37+
38+
if err := os.MkdirAll(dir, 0755); err != nil {
39+
return err
40+
}
41+
42+
// global config support
43+
cfg, _ := config.ReadConfig()
44+
45+
k8s := K8sConfig{
46+
Namespace: inferOrDefault(cfg.Defaults.Namespace, name),
47+
Context: inferOrDefault(cfg.Defaults.Context, ""),
48+
}
49+
50+
helm := HelmConfig{
51+
Release: name,
52+
Chart: "./helm/chart",
53+
Values: "./values.yaml",
54+
}
55+
56+
gitops := GitOpsConfig{
57+
Repo: "",
58+
Path: "",
59+
Branch: "main",
60+
}
61+
62+
values := ValuesConfig{}
63+
values.Image.Repository = inferOrDefault(cfg.Defaults.Image, "codewise")
64+
values.Image.Tag = inferOrDefault(cfg.Defaults.ImageTag, "latest")
65+
66+
if err := writeYAML(filepath.Join(dir, "k8s.yaml"), k8s); err != nil {
67+
return err
68+
}
69+
if err := writeYAML(filepath.Join(dir, "helm.yaml"), helm); err != nil {
70+
return err
71+
}
72+
if err := writeYAML(filepath.Join(dir, "gitops.yaml"), gitops); err != nil {
73+
return err
74+
}
75+
if err := writeYAML(filepath.Join(dir, "values.yaml"), values); err != nil {
76+
return err
77+
}
78+
79+
return nil
80+
}
81+
82+
// used by interactive path
83+
func CreateEnvFromParts(name string, k8s K8sConfig, helm HelmConfig, gitops GitOpsConfig, values ValuesConfig) error {
84+
if err := ensureBaseDir(); err != nil {
85+
return err
86+
}
87+
88+
if envExists(name) {
89+
return fmt.Errorf("environment %q already exists", name)
90+
}
91+
92+
dir, err := envDir(name)
93+
if err != nil {
94+
return err
95+
}
96+
97+
if err := os.MkdirAll(dir, 0755); err != nil {
98+
return err
99+
}
100+
101+
if err := writeYAML(filepath.Join(dir, "k8s.yaml"), k8s); err != nil {
102+
return err
103+
}
104+
if err := writeYAML(filepath.Join(dir, "helm.yaml"), helm); err != nil {
105+
return err
106+
}
107+
if err := writeYAML(filepath.Join(dir, "gitops.yaml"), gitops); err != nil {
108+
return err
109+
}
110+
if err := writeYAML(filepath.Join(dir, "values.yaml"), values); err != nil {
111+
return err
112+
}
113+
114+
return nil
115+
}
116+
117+
func writeYAML(path string, data interface{}) error {
118+
out, err := yaml.Marshal(data)
119+
if err != nil {
120+
return err
121+
}
122+
return os.WriteFile(path, out, 0644)
123+
}
124+
125+
func inferOrDefault(cfgVal, fallback string) string {
126+
if cfgVal != "" {
127+
return cfgVal
128+
}
129+
return fallback
130+
}

pkg/env/delete.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
type DeleteOptions struct {
9+
Force bool // mapped from --yes
10+
}
11+
12+
func DeleteEnv(name string, opts DeleteOptions) error {
13+
if !envExists(name) {
14+
return fmt.Errorf("environment %q does not exist", name)
15+
}
16+
17+
dir, err := envDir(name)
18+
if err != nil {
19+
return err
20+
}
21+
22+
return os.RemoveAll(dir)
23+
}

0 commit comments

Comments
 (0)