github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/step/step_replicate.go (about) 1 package step 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/jenkins-x/jx/v2/pkg/cmd/opts/step" 9 "github.com/jenkins-x/jx/v2/pkg/kube" 10 "github.com/pkg/errors" 11 "github.com/rollout/rox-go/core/utils" 12 13 "github.com/jenkins-x/jx/v2/pkg/cmd/helper" 14 "github.com/jenkins-x/jx/v2/pkg/util" 15 16 "github.com/jenkins-x/jx/v2/pkg/cmd/opts" 17 "github.com/jenkins-x/jx/v2/pkg/cmd/templates" 18 "github.com/spf13/cobra" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 ) 21 22 // ReplicateOptions contains the command line flags 23 type ReplicateOptions struct { 24 step.StepOptions 25 ReplicateToNamepace []string 26 CreateNamespace bool 27 } 28 29 const ( 30 annotationReplicationAllowed = "replicator.v1.mittwald.de/replication-allowed" 31 annotationEeplicationAllowedNamespaces = "replicator.v1.mittwald.de/replication-allowed-namespaces" 32 configMap = "configmap" 33 secret = "secret" // pragma: allowlist secret 34 replicateToNamespaceFlag = "replicate-to-namespace" 35 36 // Description common text for long command descriptions around storage 37 Description = ` 38 Annotates a secret or configmap so it can be replicated across an environment 39 ` 40 ) 41 42 var ( 43 stepReplicateLong = templates.LongDesc(` 44 45 Works with the replicator app https://github.com/jenkins-x-charts/kubernetes-replicator 46 47 jx add app replicator 48 49 This step will annotate a secret or configmap so that the replicator can replicate the data into another namespace 50 `) 51 52 stepReplicateExample = templates.Examples(` 53 NOTE: quote and namespaces that include a wildcard 54 # lets collect some files to the team's default storage location (which if not configured uses the current git repository's gh-pages branch) 55 jx step replicate configmap foo --replicate-to-namespace jx-staging --replicate-to-namespace "foo-preview*" 56 57 # lets collect some files to the team's default storage location (which if not configured uses the current git repository's gh-pages branch) 58 jx step replicate secret bar --replicate-to-namespace jx-staging --replicate-to-namespace "foo-preview*" 59 60 `) 61 ) 62 63 // NewCmdStepReplicate creates the CLI command 64 func NewCmdStepReplicate(commonOpts *opts.CommonOptions) *cobra.Command { 65 options := ReplicateOptions{ 66 StepOptions: step.StepOptions{ 67 68 CommonOptions: commonOpts, 69 }, 70 } 71 cmd := &cobra.Command{ 72 Use: "replicate", 73 Short: Description, 74 Long: stepReplicateLong, 75 Example: stepReplicateExample, 76 Run: func(cmd *cobra.Command, args []string) { 77 options.Cmd = cmd 78 options.Args = args 79 err := options.Run() 80 helper.CheckErr(err) 81 }, 82 } 83 cmd.Flags().StringArrayVarP(&options.ReplicateToNamepace, replicateToNamespaceFlag, "r", nil, "Specify a list of namespaces to replicate data into") 84 cmd.Flags().BoolVarP(&options.CreateNamespace, "create-namespace", "", false, "Should create any missing namespaces") 85 86 return cmd 87 } 88 89 // Run runs the command 90 func (o *ReplicateOptions) Run() error { 91 if len(o.Args) != 2 { 92 return util.MissingArgument("configmap or secret") 93 } 94 if o.Args[0] != secret && o.Args[0] != configMap { 95 return util.MissingArgument("configmap or secret") 96 } 97 if len(o.ReplicateToNamepace) == 0 { 98 return util.MissingOption(replicateToNamespaceFlag) 99 } 100 101 client, currentNamespace, err := o.KubeClientAndNamespace() 102 if err != nil { 103 return err 104 } 105 for _, ns := range o.ReplicateToNamepace { 106 // if there's a wildcard in the name let's not validate it exists 107 if strings.Contains(ns, "*") { 108 continue 109 } 110 _, err := client.CoreV1().Namespaces().Get(ns, metav1.GetOptions{}) 111 if err != nil { 112 if o.CreateNamespace { 113 err = kube.EnsureNamespaceCreated(client, ns, nil, nil) 114 if err != nil { 115 return err 116 } 117 } else { 118 return util.InvalidOptionError(replicateToNamespaceFlag, ns, err) 119 } 120 } 121 } 122 resourceType := o.Args[0] 123 resourceName := o.Args[1] 124 125 if resourceType == secret { 126 // find all secrets issued by letsencrypt 127 if strings.Contains(resourceName, "*") { 128 secrets, err := client.CoreV1().Secrets(currentNamespace).List(metav1.ListOptions{}) 129 if err != nil { 130 return errors.Wrap(err, fmt.Sprintf("unable to list secrets '%s'", resourceName)) 131 } 132 133 re := regexp.MustCompile(resourceName) 134 135 for _, secretItem := range secrets.Items { 136 if re.MatchString(secretItem.Name) { 137 secret, err := client.CoreV1().Secrets(currentNamespace).Get(secretItem.Name, metav1.GetOptions{}) 138 if err != nil { 139 return errors.Wrap(err, fmt.Sprintf("unable to get secret '%s'", secretItem.Name)) 140 } 141 142 secret.Annotations = setReplicatorAnnotations(secret.Annotations, o.ReplicateToNamepace) 143 _, err = client.CoreV1().Secrets(currentNamespace).Update(secret) 144 if err != nil { 145 return errors.Wrap(err, fmt.Sprintf("unable to update secret '%s'", secretItem.Name)) 146 } 147 } 148 } 149 } else { 150 secret, err := client.CoreV1().Secrets(currentNamespace).Get(resourceName, metav1.GetOptions{}) 151 if err != nil { 152 return errors.Wrap(err, fmt.Sprintf("unable to get secret '%s'", resourceName)) 153 } 154 secret.Annotations = setReplicatorAnnotations(secret.Annotations, o.ReplicateToNamepace) 155 _, err = client.CoreV1().Secrets(currentNamespace).Update(secret) 156 if err != nil { 157 return errors.Wrap(err, fmt.Sprintf("unable to update secret '%s'", resourceName)) 158 } 159 } 160 } 161 162 if resourceType == configMap { 163 cm, err := client.CoreV1().ConfigMaps(currentNamespace).Get(resourceName, metav1.GetOptions{}) 164 if err != nil { 165 return errors.Wrap(err, fmt.Sprintf("unable to get configmap %s", resourceName)) 166 } 167 cm.Annotations = setReplicatorAnnotations(cm.Annotations, o.ReplicateToNamepace) 168 _, err = client.CoreV1().ConfigMaps(currentNamespace).Update(cm) 169 if err != nil { 170 return errors.Wrap(err, fmt.Sprintf("unable to update configmap %s", resourceName)) 171 } 172 } 173 return nil 174 } 175 176 func setReplicatorAnnotations(annotations map[string]string, namespaces []string) map[string]string { 177 if annotations == nil { 178 annotations = make(map[string]string) 179 } 180 annotations[annotationReplicationAllowed] = "true" 181 allowedNamespaces := annotations[annotationEeplicationAllowedNamespaces] 182 var nsSlice []string 183 if allowedNamespaces != "" { 184 nsSlice = strings.Split(allowedNamespaces, ",") 185 } 186 for _, replicateToNamespace := range namespaces { 187 if !utils.ContainsString(nsSlice, replicateToNamespace) { 188 nsSlice = append(nsSlice, replicateToNamespace) 189 } 190 } 191 annotations[annotationEeplicationAllowedNamespaces] = strings.Join(nsSlice, ",") 192 193 return annotations 194 }