Skip to content

Commit

Permalink
Skip prod namespaces from destroy unless flag is set to false (#428)
Browse files Browse the repository at this point in the history
* Skip prod namespaces from destroy unless flag is set to false

---------

Co-authored-by: poornima-krishnasamy <[email protected]>
  • Loading branch information
poornima-krishnasamy and poornima-krishnasamy committed Jul 14, 2023
1 parent 95cffed commit 8d461a0
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 22 deletions.
3 changes: 2 additions & 1 deletion doc/cloud-platform_environment_apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ $ cloud-platform environment apply -n <namespace>

```
--all-namespaces Apply all namespaces with -all-namespaces
--cluster string folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name
--cluster string cluster context fron kubeconfig file
--clusterdir string folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name
--enable-apply-skip Enable skipping apply for a namespace
--github-token string Personal access Token from Github
-h, --help help for apply
Expand Down
4 changes: 3 additions & 1 deletion doc/cloud-platform_environment_destroy.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ $ cloud-platform environment destroy -n <namespace>
### Options

```
--cluster string folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name
--cluster string cluster context fron kubeconfig file
--clusterdir string folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name
--github-token string Personal access Token from Github
-h, --help help for destroy
--kubecfg string path to kubeconfig file (default "/home/runner/.kube/config")
-n, --namespace string Namespace which you want to perform the destroy
--prNumber int Pull request ID or number to which you want to perform the destroy
--redact Redact the terraform output before printing (default true)
--skip-prod-destroy skip prod namespaces from destroy namespace (default true)
```

### Options inherited from parent commands
Expand Down
3 changes: 2 additions & 1 deletion doc/cloud-platform_environment_plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ $ cloud-platform environment plan
### Options

```
--cluster string folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name
--cluster string cluster context fron kubeconfig file
--clusterdir string folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name
--github-token string Personal access Token from Github
-h, --help help for plan
--kubecfg string path to kubeconfig file (default "/home/runner/.kube/config")
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
github.com/jedib0t/go-pretty/v6 v6.4.3
github.com/kelseyhightower/envconfig v1.4.0
github.com/migueleliasweb/go-github-mock v0.0.16
github.com/ministryofjustice/cloud-platform-environments v1.2.1-0.20230712165212-61f4971d3baa
github.com/ministryofjustice/cloud-platform-go-library v0.0.0-20220803122921-1ca1153b1730
github.com/rs/zerolog v1.29.0
github.com/shurcooL/githubv4 v0.0.0-20220922232305-70b4d362a8cb
Expand Down Expand Up @@ -99,6 +100,7 @@ require (
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/ministryofjustice/cloud-platform-how-out-of-date-are-we/reports/pkg/hoodaw v0.0.0-20230210112905-1665bd8fd8d4 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand Down Expand Up @@ -133,7 +135,7 @@ require (
k8s.io/component-base v0.26.3 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/metrics v0.26.0-alpha.2 // indirect
k8s.io/metrics v0.26.1 // indirect
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,12 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/migueleliasweb/go-github-mock v0.0.16 h1:iEx6iqYASRJVoEO5eMOYpQZFTc00cZ6ysynOArUKM3A=
github.com/migueleliasweb/go-github-mock v0.0.16/go.mod h1:CjrgPd8s5sf5g3XSESAQqxufae+PZbgM/F317C3uD7g=
github.com/ministryofjustice/cloud-platform-environments v1.2.1-0.20230712165212-61f4971d3baa h1:ttFqd4Ks4CizU51l7aKp3XuEI87t20VoRnn6O8ARj9Q=
github.com/ministryofjustice/cloud-platform-environments v1.2.1-0.20230712165212-61f4971d3baa/go.mod h1:i3PdaTg3t4U25VUT4qkUn5MNVGhyP1sSnSnTp6UV7OE=
github.com/ministryofjustice/cloud-platform-go-library v0.0.0-20220803122921-1ca1153b1730 h1:4u9OcC1NTIARZjKBdop1qY3hxC2DGaHEE+i43BFh+aI=
github.com/ministryofjustice/cloud-platform-go-library v0.0.0-20220803122921-1ca1153b1730/go.mod h1:53gzeRuRULbIkftuejJrv41aJl8ps35D9qKX2rtkwOc=
github.com/ministryofjustice/cloud-platform-how-out-of-date-are-we/reports/pkg/hoodaw v0.0.0-20230210112905-1665bd8fd8d4 h1:ftBQt/WDx6KDlxZWDRKAEps1lOgg4Po0IDsD9mqAg48=
github.com/ministryofjustice/cloud-platform-how-out-of-date-are-we/reports/pkg/hoodaw v0.0.0-20230210112905-1665bd8fd8d4/go.mod h1:MBNDOQ9eBV1V9rbtuQOiXYkhSyuytZwK2J2m44+rgMk=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
Expand Down Expand Up @@ -869,8 +873,8 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
k8s.io/kubectl v0.26.0-alpha.2 h1:oVGhSH2LyUCJtnkh2x/mF1Zrd6gNoMoPzZQn9OQgrd4=
k8s.io/kubectl v0.26.0-alpha.2/go.mod h1:PJGh0Ga44aosnto+H/CuYoqQFFvILdWH0fFy8XrSOAg=
k8s.io/metrics v0.26.0-alpha.2 h1:3hBWB8ji5brwC2Pd0AoexlOF04tgzwnEeUg43wmzyD8=
k8s.io/metrics v0.26.0-alpha.2/go.mod h1:6S/TGdomnTocg+e+gf9AWDAah9DTaqSs9lRhq2ivWmU=
k8s.io/metrics v0.26.1 h1:iB+QdMLa2V70a7zb0XYEcaUpPM0y+p4fZN0UtxcPHLk=
k8s.io/metrics v0.26.1/go.mod h1:fMeLXmK/xgvckFG63GJ0kDjFiQH7P0Dpi5Lvhlo5DXE=
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
Expand Down
10 changes: 7 additions & 3 deletions pkg/commands/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func addEnvironmentCmd(topLevel *cobra.Command) {
// Re-use the environmental variable TF_VAR_github_token to call Github Client which is needed to perform terraform operations on each namespace
environmentApplyCmd.Flags().StringVar(&optFlags.GithubToken, "github-token", os.Getenv("TF_VAR_github_token"), "Personal access Token from Github ")
environmentApplyCmd.Flags().StringVar(&optFlags.KubecfgPath, "kubecfg", filepath.Join(homedir.HomeDir(), ".kube", "config"), "path to kubeconfig file")
environmentApplyCmd.Flags().StringVar(&optFlags.ClusterCtx, "cluster", "", "folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name")
environmentApplyCmd.Flags().StringVar(&optFlags.ClusterCtx, "cluster", "", "cluster context fron kubeconfig file")
environmentApplyCmd.Flags().StringVar(&optFlags.ClusterDir, "clusterdir", "", "folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name")
environmentApplyCmd.PersistentFlags().BoolVar(&optFlags.RedactedEnv, "redact", true, "Redact the terraform output before printing")

environmentBumpModuleCmd.Flags().StringVarP(&module, "module", "m", "", "Module to upgrade the version")
Expand All @@ -77,8 +78,10 @@ func addEnvironmentCmd(topLevel *cobra.Command) {
// Re-use the environmental variable TF_VAR_github_token to call Github Client which is needed to perform terraform operations on each namespace
environmentDestroyCmd.Flags().StringVar(&optFlags.GithubToken, "github-token", os.Getenv("TF_VAR_github_token"), "Personal access Token from Github ")
environmentDestroyCmd.Flags().StringVar(&optFlags.KubecfgPath, "kubecfg", filepath.Join(homedir.HomeDir(), ".kube", "config"), "path to kubeconfig file")
environmentDestroyCmd.Flags().StringVar(&optFlags.ClusterCtx, "cluster", "", "folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name")
environmentDestroyCmd.Flags().StringVar(&optFlags.ClusterCtx, "cluster", "", "cluster context fron kubeconfig file")
environmentDestroyCmd.Flags().StringVar(&optFlags.ClusterDir, "clusterdir", "", "folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name")
environmentDestroyCmd.PersistentFlags().BoolVar(&optFlags.RedactedEnv, "redact", true, "Redact the terraform output before printing")
environmentDestroyCmd.Flags().BoolVar(&optFlags.SkipProdDestroy, "skip-prod-destroy", true, "skip prod namespaces from destroy namespace")

environmentDivergenceCmd.Flags().StringVarP(&clusterName, "cluster-name", "c", "live", "[optional] Cluster name")
environmentDivergenceCmd.Flags().StringVarP(&githubToken, "github-token", "g", "", "[required] Github token")
Expand All @@ -94,7 +97,8 @@ func addEnvironmentCmd(topLevel *cobra.Command) {
// Re-use the environmental variable TF_VAR_github_token to call Github Client which is needed to perform terraform operations on each namespace
environmentPlanCmd.Flags().StringVar(&optFlags.GithubToken, "github-token", os.Getenv("TF_VAR_github_token"), "Personal access Token from Github ")
environmentPlanCmd.Flags().StringVar(&optFlags.KubecfgPath, "kubecfg", filepath.Join(homedir.HomeDir(), ".kube", "config"), "path to kubeconfig file")
environmentPlanCmd.Flags().StringVar(&optFlags.ClusterCtx, "cluster", "", "folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name")
environmentPlanCmd.Flags().StringVar(&optFlags.ClusterCtx, "cluster", "", "cluster context fron kubeconfig file")
environmentPlanCmd.Flags().StringVar(&optFlags.ClusterDir, "clusterdir", "", "folder name under namespaces/ inside cloud-platform-environments repo refering to full cluster name")
environmentPlanCmd.PersistentFlags().BoolVar(&optFlags.RedactedEnv, "redact", true, "Redact the terraform output before printing")

}
Expand Down
55 changes: 42 additions & 13 deletions pkg/environment/environmentApply.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import (
"github.com/kelseyhightower/envconfig"
"github.com/ministryofjustice/cloud-platform-cli/pkg/github"
"github.com/ministryofjustice/cloud-platform-cli/pkg/util"
"github.com/ministryofjustice/cloud-platform-environments/pkg/authenticate"
"github.com/ministryofjustice/cloud-platform-environments/pkg/namespace"
v1 "k8s.io/api/core/v1"
)

// Options are used to configure plan/apply sessions.
// These options are normally passed via flags in a command line.
type Options struct {
Namespace, KubecfgPath, ClusterCtx, GithubToken string
PRNumber int
AllNamespaces bool
EnableApplySkip, RedactedEnv bool
Namespace, KubecfgPath, ClusterCtx, ClusterDir, GithubToken string
PRNumber int
AllNamespaces bool
EnableApplySkip, RedactedEnv, SkipProdDestroy bool
}

// RequiredEnvVars is used to store values such as TF_VAR_ , github and pingdom tokens
Expand Down Expand Up @@ -48,7 +51,7 @@ func NewApply(opt Options) *Apply {
apply := Apply{
Options: &opt,
Applier: NewApplier("/usr/local/bin/terraform", "/usr/local/bin/kubectl"),
Dir: "namespaces/" + opt.ClusterCtx + "/" + opt.Namespace,
Dir: "namespaces/" + opt.ClusterDir + "/" + opt.Namespace,
}

apply.Initialize()
Expand Down Expand Up @@ -97,7 +100,7 @@ func (a *Apply) Plan() error {
if err != nil {
return fmt.Errorf("failed to fetch list of changed files: %s in PR %v", err, a.Options.PRNumber)
}
changedNamespaces, err := nsChangedInPR(files, a.Options.ClusterCtx, false)
changedNamespaces, err := nsChangedInPR(files, a.Options.ClusterDir, false)
if err != nil {
return err
}
Expand Down Expand Up @@ -139,7 +142,7 @@ func (a *Apply) Apply() error {
return err
}

changedNamespaces, err := nsChangedInPR(repos, a.Options.ClusterCtx, false)
changedNamespaces, err := nsChangedInPR(repos, a.Options.ClusterDir, false)
if err != nil {
return err
}
Expand Down Expand Up @@ -173,13 +176,29 @@ func (a *Apply) Destroy() error {
return err
}
if isMerged {
changedNamespaces, err := a.nsCreateRawChangedFilesInPR(a.Options.ClusterCtx, a.Options.PRNumber)
changedNamespaces, err := a.nsCreateRawChangedFilesInPR(a.Options.ClusterDir, a.Options.PRNumber)
fmt.Println("Namespaces changed in PR", changedNamespaces)
if err != nil {
return err
}
kubeClient, err := authenticate.CreateClientFromConfigFile(a.Options.KubecfgPath, a.Options.ClusterCtx)
if err != nil {
return err
}

// GetAllNamespacesFromCluster
namespaces, err := namespace.GetAllNamespacesFromCluster(kubeClient)
if err != nil {
return err
}

for _, namespace := range changedNamespaces {
a.Options.Namespace = namespace
if a.Options.SkipProdDestroy && isProductionNs(namespace, namespaces) {
err := fmt.Errorf("cannot destroy production namespace with skip-prod-destroy flag set to true")
return err
}
// Check if the namespace is present in the folder
if _, err = os.Stat(a.Options.Namespace); err != nil {
fmt.Println("Destroying Namespace:", namespace)
err = a.destroyNamespace()
Expand All @@ -204,7 +223,7 @@ func (a *Apply) ApplyAll() error {
return err
}

repoPath := "namespaces/" + a.Options.ClusterCtx
repoPath := "namespaces/" + a.Options.ClusterDir
folderChunks, err := util.GetFolderChunks(repoPath, numRoutines)
if err != nil {
return err
Expand Down Expand Up @@ -328,7 +347,7 @@ func (a *Apply) destroyTerraform() (string, error) {
// applyKubectl with dry-run enabled and calls applier TerraformInitAndPlan and prints the output
func (a *Apply) planNamespace() error {
applier := NewApply(*a.Options)
repoPath := "namespaces/" + a.Options.ClusterCtx + "/" + a.Options.Namespace
repoPath := "namespaces/" + a.Options.ClusterDir + "/" + a.Options.Namespace

if util.IsYamlFileExists(repoPath) {
outputKubectl, err := applier.planKubectl()
Expand Down Expand Up @@ -388,7 +407,7 @@ func (a *Apply) applyNamespace() error {
// secretBlocker is a file used to control the behaviour of a namespace that will have all
// secrets in a namespace rotated. This came out of the requirement to rotate IAM credentials
// post circle breach.
repoPath := "namespaces/" + a.Options.ClusterCtx + "/" + a.Options.Namespace
repoPath := "namespaces/" + a.Options.ClusterDir + "/" + a.Options.Namespace

if _, err := os.Stat(repoPath); os.IsNotExist(err) {
fmt.Printf("Namespace %s does not exist, skipping apply\n", a.Options.Namespace)
Expand Down Expand Up @@ -443,7 +462,7 @@ func (a *Apply) applyNamespace() error {
// destroyNamespace intiates a apply object with options and env variables, and calls the
// calls applier TerraformInitAndDestroy, applyKubectl with dry-run disabled and prints the output
func (a *Apply) destroyNamespace() error {
repoPath := "namespaces/" + a.Options.ClusterCtx + "/" + a.Options.Namespace
repoPath := "namespaces/" + a.Options.ClusterDir + "/" + a.Options.Namespace

if _, err := os.Stat(repoPath); os.IsNotExist(err) {
fmt.Printf("Namespace %s does not exist, skipping destroy\n", a.Options.Namespace)
Expand Down Expand Up @@ -487,7 +506,7 @@ func (a *Apply) nsCreateRawChangedFilesInPR(cluster string, prNumber int) ([]str
return nil, fmt.Errorf("failed to fetch list of changed files: %s", err)
}

namespaces, err := nsChangedInPR(files, a.Options.ClusterCtx, true)
namespaces, err := nsChangedInPR(files, a.Options.ClusterDir, true)
if err != nil {
return nil, fmt.Errorf("failed to get namespace for destroy from the PR: %s", err)
}
Expand Down Expand Up @@ -559,3 +578,13 @@ func createNamespaceforDestroy(namespaces []string, cluster string) error {
}
return nil
}

func isProductionNs(nsInPR string, namespaces []v1.Namespace) bool {
for _, ns := range namespaces {
is_prod := ns.Labels["cloud-platform.justice.gov.uk/is-production"] == "true"
if ns.Name == nsInPR && is_prod {
return true
}
}
return false
}
56 changes: 56 additions & 0 deletions pkg/environment/environmentApply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/google/go-github/github"
"github.com/ministryofjustice/cloud-platform-cli/pkg/environment/mocks"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestApply_ApplyTerraform(t *testing.T) {
Expand Down Expand Up @@ -391,3 +393,57 @@ func TestApply_deleteKubectl(t *testing.T) {
})
}
}

func Test_isProductionNs(t *testing.T) {
type args struct {
nsInPR string
namespaces []v1.Namespace
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Prod Namespace is not in PR",
args: args{
nsInPR: "foobar",
namespaces: []v1.Namespace{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foobar",
Labels: map[string]string{
"cloud-platform.justice.gov.uk/is-production": "false",
},
},
},
},
},
want: false,
},
{
name: "Prod Namespace is in PR",
args: args{
nsInPR: "foobar",
namespaces: []v1.Namespace{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foobar",
Labels: map[string]string{
"cloud-platform.justice.gov.uk/is-production": "true",
},
},
},
},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isProductionNs(tt.args.nsInPR, tt.args.namespaces); got != tt.want {
t.Errorf("isProductionNs() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 8d461a0

Please sign in to comment.