diff --git a/README.md b/README.md index 5c18ae6..e0705bc 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ push chart Push Helm chart to chart repository get env Get list of Helm releases in an environment (Kubernetes namespace) deploy env Deploy a list of Helm charts to an environment (Kubernetes namespace) delete env Delete an environment (Kubernetes namespace) along with all Helm releases in it +lock env Lock an environment (Kubernetes namespace) +unlock env Unlock an environment (Kubernetes namespace) create resource Create or update a resource via REST API get resource Get a resource via REST API @@ -304,6 +306,30 @@ Flags: `helm-tls-store` - path to directory containing `.cert.pem` and `.key.pem` files +### Lock env +``` +Lock an environment (Kubernetes namespace) + +Usage: + orca lock env [flags] + +Flags: + --kube-context string name of the kubeconfig context to use. Overrides $ORCA_KUBE_CONTEXT + -n, --name string name of environment (namespace) to delete. Overrides $ORCA_NAME +``` + +### Unlock env +``` +Unlock an environment (Kubernetes namespace) + +Usage: + orca unlock env [flags] + +Flags: + --kube-context string name of the kubeconfig context to use. Overrides $ORCA_KUBE_CONTEXT + -n, --name string name of environment (namespace) to delete. Overrides $ORCA_NAME +``` + ### Create resource ``` Create or update a resource via REST API diff --git a/cmd/orca.go b/cmd/orca.go index 64d23be..2780776 100644 --- a/cmd/orca.go +++ b/cmd/orca.go @@ -37,6 +37,8 @@ Instead of writing scripts on top of scripts, Orca holds all the logic. cmd.AddCommand(NewPushCmd(out)) cmd.AddCommand(NewCreateCmd(out)) cmd.AddCommand(NewVersionCmd(out)) + cmd.AddCommand(NewLockCmd(out)) + cmd.AddCommand(NewUnlockCmd(out)) return cmd } @@ -96,6 +98,32 @@ func NewGetCmd(out io.Writer) *cobra.Command { return cmd } +// NewLockCmd represents the lock command +func NewLockCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "lock", + Short: "Lock functions", + Long: ``, + } + + cmd.AddCommand(orca.NewLockEnvCmd(out)) + + return cmd +} + +// NewUnlockCmd represents the unlock command +func NewUnlockCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "unlock", + Short: "Unlock functions", + Long: ``, + } + + cmd.AddCommand(orca.NewUnlockEnvCmd(out)) + + return cmd +} + // NewPushCmd represents the get command func NewPushCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ diff --git a/pkg/orca/env.go b/pkg/orca/env.go index 18a91ff..8ae8ea9 100644 --- a/pkg/orca/env.go +++ b/pkg/orca/env.go @@ -108,25 +108,26 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command { }, Run: func(cmd *cobra.Command, args []string) { print := false + + utils.AddRepository(e.repo, print) + utils.UpdateRepositories(print) + nsPreExists := true if !utils.NamespaceExists(e.name, e.kubeContext) { nsPreExists = false utils.CreateNamespace(e.name, e.kubeContext, print) log.Printf("created environment \"%s\"", e.name) } - - utils.AddRepository(e.repo, print) - utils.UpdateRepositories(print) + lockEnvironment(e.name, e.kubeContext, print) var desiredReleases []utils.ReleaseSpec if nsPreExists && e.deployOnlyOverrideIfEnvExists { desiredReleases = utils.InitReleases(e.name, e.override) } else { desiredReleases = utils.InitReleasesFromChartsFile(e.chartsFile, e.name) - desiredReleases = utils.OverrideReleases(desiredReleases, e.override) + desiredReleases = utils.OverrideReleases(desiredReleases, e.override, e.name) } - lockEnvironment(e.name, e.kubeContext, print) includeFailed := false installedReleases := utils.GetInstalledReleases(e.kubeContext, e.name, includeFailed) releasesToInstall := utils.GetReleasesDelta(desiredReleases, installedReleases) @@ -204,6 +205,72 @@ func NewDeleteEnvCmd(out io.Writer) *cobra.Command { return cmd } +// NewLockEnvCmd represents the lock env command +func NewLockEnvCmd(out io.Writer) *cobra.Command { + e := &envCmd{out: out} + + cmd := &cobra.Command{ + Use: "env", + Short: "Lock an environment (Kubernetes namespace)", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if e.name == "" { + return errors.New("name can not be empty") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if !utils.NamespaceExists(e.name, e.kubeContext) { + log.Printf("environment \"%s\" not found", e.name) + return + } + print := false + lockEnvironment(e.name, e.kubeContext, print) + log.Printf("locked environment \"%s\"", e.name) + }, + } + + f := cmd.Flags() + + f.StringVarP(&e.name, "name", "n", os.Getenv("ORCA_NAME"), "name of environment (namespace) to delete. Overrides $ORCA_NAME") + f.StringVar(&e.kubeContext, "kube-context", os.Getenv("ORCA_KUBE_CONTEXT"), "name of the kubeconfig context to use. Overrides $ORCA_KUBE_CONTEXT") + + return cmd +} + +// NewUnlockEnvCmd represents the unlock env command +func NewUnlockEnvCmd(out io.Writer) *cobra.Command { + e := &envCmd{out: out} + + cmd := &cobra.Command{ + Use: "env", + Short: "Unlock an environment (Kubernetes namespace)", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if e.name == "" { + return errors.New("name can not be empty") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if !utils.NamespaceExists(e.name, e.kubeContext) { + log.Printf("environment \"%s\" not found", e.name) + return + } + print := false + unlockEnvironment(e.name, e.kubeContext, print) + log.Printf("unlocked environment \"%s\"", e.name) + }, + } + + f := cmd.Flags() + + f.StringVarP(&e.name, "name", "n", os.Getenv("ORCA_NAME"), "name of environment (namespace) to delete. Overrides $ORCA_NAME") + f.StringVar(&e.kubeContext, "kube-context", os.Getenv("ORCA_KUBE_CONTEXT"), "name of the kubeconfig context to use. Overrides $ORCA_KUBE_CONTEXT") + + return cmd +} + const ( annotationPrefix string = "orca.nuvocares.com" stateAnnotation string = annotationPrefix + "/state" diff --git a/pkg/utils/chart.go b/pkg/utils/chart.go index b96af18..ab21e78 100644 --- a/pkg/utils/chart.go +++ b/pkg/utils/chart.go @@ -121,14 +121,20 @@ func CheckCircularDependencies(releases []ReleaseSpec) bool { return false } -func OverrideReleases(releases []ReleaseSpec, overrides []string) []ReleaseSpec { +func OverrideReleases(releases []ReleaseSpec, overrides []string, env string) []ReleaseSpec { + if len(overrides) == 0 { + return releases + } + var outReleases []ReleaseSpec + var overrideFound = make([]bool, len(overrides)) for _, r := range releases { - for _, override := range overrides { - oChartName, oChartVersion := SplitInTwo(override, "=") + for i := 0; i < len(overrides); i++ { + oChartName, oChartVersion := SplitInTwo(overrides[i], "=") if r.ChartName == oChartName && r.ChartVersion != oChartVersion { + overrideFound[i] = true r.ChartName = oChartName r.ChartVersion = oChartVersion } @@ -136,6 +142,19 @@ func OverrideReleases(releases []ReleaseSpec, overrides []string) []ReleaseSpec outReleases = append(outReleases, r) } + for i := 0; i < len(overrides); i++ { + if overrideFound[i] { + continue + } + oChartName, oChartVersion := SplitInTwo(overrides[i], "=") + r := ReleaseSpec{ + ReleaseName: env + "-" + oChartName, + ChartName: oChartName, + ChartVersion: oChartVersion, + } + outReleases = append(outReleases, r) + } + return outReleases } diff --git a/pkg/utils/general.go b/pkg/utils/general.go index a8c83d6..f1ad4bc 100644 --- a/pkg/utils/general.go +++ b/pkg/utils/general.go @@ -21,6 +21,7 @@ func Exec(cmd string) string { output, err := exec.Command(binary, args[1:]...).CombinedOutput() if err != nil { + log.Println("Error: command execution failed:", cmd) log.Fatal(string(output)) } return string(output) diff --git a/test/chart_test.go b/test/chart_test.go index 86bd55f..38e856d 100644 --- a/test/chart_test.go +++ b/test/chart_test.go @@ -66,12 +66,28 @@ func TestOverrideReleases_WithOverride(t *testing.T) { releases := []utils.ReleaseSpec{rel0, rel1, rel2} - overrideReleases := utils.OverrideReleases(releases, []string{"kaa=7.1.0"}) + overrideReleases := utils.OverrideReleases(releases, []string{"kaa=7.1.0"}, "test") if !overrideReleases[2].Equals(rel2override) { t.Errorf("Expected: true, Actual: false") } } + +func TestOverrideReleases_WithNewOverride(t *testing.T) { + rel0 := utils.ReleaseSpec{ChartName: "cassandra", ChartVersion: "0.4.0", ReleaseName: "test-cassandra"} + rel1 := utils.ReleaseSpec{ChartName: "mariadb", ChartVersion: "0.5.4", ReleaseName: "test-mariadb"} + rel2 := utils.ReleaseSpec{ChartName: "kaa", ChartVersion: "0.1.7", ReleaseName: "test-kaa"} + rel2override := utils.ReleaseSpec{ChartName: "example", ChartVersion: "3.3.3", ReleaseName: "test-example"} + + releases := []utils.ReleaseSpec{rel0, rel1, rel2} + + overrideReleases := utils.OverrideReleases(releases, []string{"example=3.3.3"}, "test") + + if !overrideReleases[3].Equals(rel2override) { + t.Errorf("Expected: true, Actual: false") + } +} + func TestOverrideReleases_WithoutOverride(t *testing.T) { rel0 := utils.ReleaseSpec{ChartName: "cassandra", ChartVersion: "0.4.0", ReleaseName: "test-cassandra"} rel1 := utils.ReleaseSpec{ChartName: "mariadb", ChartVersion: "0.5.4", ReleaseName: "test-mariadb"} @@ -79,7 +95,7 @@ func TestOverrideReleases_WithoutOverride(t *testing.T) { releases := []utils.ReleaseSpec{rel0, rel1, rel2} - overrideReleases := utils.OverrideReleases(releases, []string{}) + overrideReleases := utils.OverrideReleases(releases, []string{}, "test") if !overrideReleases[0].Equals(rel0) { t.Errorf("Expected: true, Actual: false")