Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cmd/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cmd

import "github.com/spf13/cobra"

var envCmd = &cobra.Command{
Use: "env",
Short: "Manage deployment environments",
Long: "Create, list, and delete Codewise deployment environments.",
}

func init() {
rootCmd.AddCommand(envCmd)
}
104 changes: 104 additions & 0 deletions cmd/env_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package cmd

import (
"fmt"

survey "github.com/AlecAivazis/survey/v2"
"github.com/aryansharma9917/codewise-cli/pkg/config"
"github.com/aryansharma9917/codewise-cli/pkg/env"
"github.com/spf13/cobra"
)

var interactive bool

var envCreateCmd = &cobra.Command{
Use: "create <name>",
Short: "Create a new environment",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]

if interactive {
if err := createEnvInteractive(name); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("environment", name, "created")
return
}

if err := env.CreateEnv(name, env.CreateOptions{}); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("environment", name, "created")
},
}

func init() {
envCreateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Enable interactive mode")
envCmd.AddCommand(envCreateCmd)
}

func createEnvInteractive(name string) error {
cfg, _ := config.ReadConfig()

defaultNs := fallback(cfg.Defaults.Namespace, name)
defaultCtx := fallback(cfg.Defaults.Context, "")
defaultRepo := fallback(cfg.Defaults.RepoURL, "")
defaultBranch := fallback(cfg.Defaults.Branch, "main")
defaultImage := fallback(cfg.Defaults.Image, "codewise")
defaultTag := fallback(cfg.Defaults.ImageTag, "latest")

answers := struct {
Namespace string
Context string
Repo string
Branch string
Image string
Tag string
}{}

qs := []*survey.Question{
{Name: "Namespace", Prompt: &survey.Input{Message: fmt.Sprintf("Namespace (default: %s)", defaultNs)}},
{Name: "Context", Prompt: &survey.Input{Message: fmt.Sprintf("Kubernetes context (default: %s)", defaultCtx)}},
{Name: "Repo", Prompt: &survey.Input{Message: fmt.Sprintf("GitOps repo (default: %s)", defaultRepo)}},
{Name: "Branch", Prompt: &survey.Input{Message: fmt.Sprintf("GitOps branch (default: %s)", defaultBranch)}},
{Name: "Image", Prompt: &survey.Input{Message: fmt.Sprintf("Image repository (default: %s)", defaultImage)}},
{Name: "Tag", Prompt: &survey.Input{Message: fmt.Sprintf("Image tag (default: %s)", defaultTag)}},
}

if err := survey.Ask(qs, &answers); err != nil {
return err
}

k8s := env.K8sConfig{
Namespace: fallback(answers.Namespace, defaultNs),
Context: fallback(answers.Context, defaultCtx),
}

helm := env.HelmConfig{
Release: name,
Chart: "./helm/chart",
Values: "./values.yaml",
}

gitops := env.GitOpsConfig{
Repo: fallback(answers.Repo, defaultRepo),
Path: "",
Branch: fallback(answers.Branch, defaultBranch),
}

values := env.ValuesConfig{}
values.Image.Repository = fallback(answers.Image, defaultImage)
values.Image.Tag = fallback(answers.Tag, defaultTag)

return env.CreateEnvFromParts(name, k8s, helm, gitops, values)
}

func fallback(input, def string) string {
if input != "" {
return input
}
return def
}
47 changes: 47 additions & 0 deletions cmd/env_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cmd

import (
"fmt"

survey "github.com/AlecAivazis/survey/v2"
"github.com/aryansharma9917/codewise-cli/pkg/env"
"github.com/spf13/cobra"
)

var yes bool

var envDeleteCmd = &cobra.Command{
Use: "delete <name>",
Short: "Delete an environment",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]

if !yes {
var confirm bool
prompt := &survey.Confirm{
Message: fmt.Sprintf("Delete environment %q?", name),
}
if err := survey.AskOne(prompt, &confirm); err != nil {
fmt.Println("error:", err)
return
}
if !confirm {
fmt.Println("aborted")
return
}
}

if err := env.DeleteEnv(name, env.DeleteOptions{Force: yes}); err != nil {
fmt.Println("error:", err)
return
}

fmt.Println("environment", name, "deleted")
},
}

func init() {
envDeleteCmd.Flags().BoolVar(&yes, "yes", false, "Skip confirmation")
envCmd.AddCommand(envDeleteCmd)
}
34 changes: 34 additions & 0 deletions cmd/env_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cmd

import (
"fmt"

"github.com/aryansharma9917/codewise-cli/pkg/env"
"github.com/spf13/cobra"
)

var envListCmd = &cobra.Command{
Use: "list",
Short: "List environments",
Run: func(cmd *cobra.Command, args []string) {
envs, err := env.ListEnvs()
if err != nil {
fmt.Println("error:", err)
return
}

if len(envs) == 0 {
fmt.Println("no environments found")
return
}

for _, e := range envs {
fmt.Printf("%-10s namespace=%s context=%s\n",
e.Name, e.K8s.Namespace, e.K8s.Context)
}
},
}

func init() {
envCmd.AddCommand(envListCmd)
}
7 changes: 6 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ type Config struct {
User struct {
Name string `yaml:"name"`
} `yaml:"user"`

Defaults struct {
AppName string `yaml:"app_name"`
Image string `yaml:"image"`
ImageTag string `yaml:"image_tag"`
RepoURL string `yaml:"repo_url"`
Namespace string `yaml:"namespace"`
Context string `yaml:"context"`
Branch string `yaml:"branch"`
} `yaml:"defaults"`
}

Expand All @@ -32,10 +35,12 @@ user:
name: aryan
defaults:
app_name: myapp
image: codewise:latest
image: codewise
image_tag: latest
repo_url: https://github.com/example/repo
namespace: default
context: ""
branch: main
`)

func InitConfig() (string, error) {
Expand Down
130 changes: 130 additions & 0 deletions pkg/env/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package env

import (
"fmt"
"os"
"path/filepath"

"github.com/aryansharma9917/codewise-cli/pkg/config"
"gopkg.in/yaml.v3"
)

type CreateOptions struct {
Interactive bool
}

func CreateEnv(name string, opts CreateOptions) error {
if opts.Interactive {
// handled at CLI layer
return fmt.Errorf("interactive mode not implemented in CreateEnv")
}
return createSilent(name)
}

func createSilent(name string) error {
if err := ensureBaseDir(); err != nil {
return err
}

if envExists(name) {
return fmt.Errorf("environment %q already exists", name)
}

dir, err := envDir(name)
if err != nil {
return err
}

if err := os.MkdirAll(dir, 0755); err != nil {
return err
}

// global config support
cfg, _ := config.ReadConfig()

k8s := K8sConfig{
Namespace: inferOrDefault(cfg.Defaults.Namespace, name),
Context: inferOrDefault(cfg.Defaults.Context, ""),
}

helm := HelmConfig{
Release: name,
Chart: "./helm/chart",
Values: "./values.yaml",
}

gitops := GitOpsConfig{
Repo: "",
Path: "",
Branch: "main",
}

values := ValuesConfig{}
values.Image.Repository = inferOrDefault(cfg.Defaults.Image, "codewise")
values.Image.Tag = inferOrDefault(cfg.Defaults.ImageTag, "latest")

if err := writeYAML(filepath.Join(dir, "k8s.yaml"), k8s); err != nil {
return err
}
if err := writeYAML(filepath.Join(dir, "helm.yaml"), helm); err != nil {
return err
}
if err := writeYAML(filepath.Join(dir, "gitops.yaml"), gitops); err != nil {
return err
}
if err := writeYAML(filepath.Join(dir, "values.yaml"), values); err != nil {
return err
}

return nil
}

// used by interactive path
func CreateEnvFromParts(name string, k8s K8sConfig, helm HelmConfig, gitops GitOpsConfig, values ValuesConfig) error {
if err := ensureBaseDir(); err != nil {
return err
}

if envExists(name) {
return fmt.Errorf("environment %q already exists", name)
}

dir, err := envDir(name)
if err != nil {
return err
}

if err := os.MkdirAll(dir, 0755); err != nil {
return err
}

if err := writeYAML(filepath.Join(dir, "k8s.yaml"), k8s); err != nil {
return err
}
if err := writeYAML(filepath.Join(dir, "helm.yaml"), helm); err != nil {
return err
}
if err := writeYAML(filepath.Join(dir, "gitops.yaml"), gitops); err != nil {
return err
}
if err := writeYAML(filepath.Join(dir, "values.yaml"), values); err != nil {
return err
}

return nil
}

func writeYAML(path string, data interface{}) error {
out, err := yaml.Marshal(data)
if err != nil {
return err
}
return os.WriteFile(path, out, 0644)
}

func inferOrDefault(cfgVal, fallback string) string {
if cfgVal != "" {
return cfgVal
}
return fallback
}
23 changes: 23 additions & 0 deletions pkg/env/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package env

import (
"fmt"
"os"
)

type DeleteOptions struct {
Force bool // mapped from --yes
}

func DeleteEnv(name string, opts DeleteOptions) error {
if !envExists(name) {
return fmt.Errorf("environment %q does not exist", name)
}

dir, err := envDir(name)
if err != nil {
return err
}

return os.RemoveAll(dir)
}
Loading