github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/step/step_replicate.go (about)

     1  package step
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/olli-ai/jx/v2/pkg/cmd/opts/step"
     9  	"github.com/olli-ai/jx/v2/pkg/kube"
    10  	"github.com/pkg/errors"
    11  	"github.com/rollout/rox-go/core/utils"
    12  
    13  	"github.com/olli-ai/jx/v2/pkg/cmd/helper"
    14  	"github.com/olli-ai/jx/v2/pkg/util"
    15  
    16  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
    17  	"github.com/olli-ai/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  }