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

     1  package pr
     2  
     3  import (
     4  	"path/filepath"
     5  	"strings"
     6  
     7  	"github.com/olli-ai/jx/v2/pkg/cmd/opts/step"
     8  
     9  	"github.com/olli-ai/jx/v2/pkg/config"
    10  
    11  	"github.com/olli-ai/jx/v2/pkg/helm"
    12  
    13  	"github.com/olli-ai/jx/v2/pkg/gits"
    14  
    15  	"github.com/olli-ai/jx/v2/pkg/versionstream"
    16  
    17  	"github.com/olli-ai/jx/v2/pkg/gits/operations"
    18  
    19  	"github.com/olli-ai/jx/v2/pkg/cmd/helper"
    20  
    21  	"github.com/olli-ai/jx/v2/pkg/cloud/gke"
    22  
    23  	"github.com/jenkins-x/jx-logging/pkg/log"
    24  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
    25  	"github.com/olli-ai/jx/v2/pkg/cmd/templates"
    26  	"github.com/olli-ai/jx/v2/pkg/util"
    27  	"github.com/pkg/errors"
    28  	"github.com/spf13/cobra"
    29  )
    30  
    31  var (
    32  	createVersionPullRequestLong = templates.LongDesc(`
    33  		Creates a Pull Request on the versions git repository for a new versionstream of a chart/package
    34  `)
    35  
    36  	createVersionPullRequestExample = templates.Examples(`
    37  		# create a Pull Request to update a chart versionstream
    38  		jx step create pr versions -n jenkins-x/prow -v 1.2.3
    39  
    40  		# create a Pull Request to update a chart versionstream to the latest found in the helm repo
    41  		jx step create pr versions -n jenkins-x/prow
    42  
    43  		# create a Pull Request to update all charts matching a filter to the latest found in the helm repo
    44  		jx step create pr versions pr -f "*"
    45  
    46  		# create a Pull Request to update all charts in the 'jenkins-x' chart repository to the latest found in the helm repo
    47  		jx step create pr versions -f "jenkins-x/*"
    48  
    49  		# create a Pull Request to update all charts in the 'jenkins-x' chart repository and update the BDD test images
    50  		jx step create pr versions -f "jenkins-x/*" --images
    51  
    52  			`)
    53  )
    54  
    55  // StepCreatePullRequestVersionsOptions contains the command line flags
    56  type StepCreatePullRequestVersionsOptions struct {
    57  	StepCreatePrOptions
    58  
    59  	Kinds              []string
    60  	Name               string
    61  	Includes           []string
    62  	Excludes           []string
    63  	UpdateTektonImages bool
    64  }
    65  
    66  // NewCmdStepCreatePullRequestVersion Creates a new Command object
    67  func NewCmdStepCreatePullRequestVersion(commonOpts *opts.CommonOptions) *cobra.Command {
    68  	options := &StepCreatePullRequestVersionsOptions{
    69  		StepCreatePrOptions: StepCreatePrOptions{
    70  			StepCreateOptions: step.StepCreateOptions{
    71  				StepOptions: step.StepOptions{
    72  					CommonOptions: commonOpts,
    73  				},
    74  			},
    75  		},
    76  	}
    77  
    78  	cmd := &cobra.Command{
    79  		Use:     "versions",
    80  		Short:   "Creates a Pull Request on the versions git repository for a new versionstream of a chart/package",
    81  		Long:    createVersionPullRequestLong,
    82  		Example: createVersionPullRequestExample,
    83  		Aliases: []string{"versionstream pullrequest", "versionstream"},
    84  		Run: func(cmd *cobra.Command, args []string) {
    85  			options.Cmd = cmd
    86  			options.Args = args
    87  			err := options.Run()
    88  			helper.CheckErr(err)
    89  		},
    90  	}
    91  	cmd.Flags().StringArrayVarP(&options.Kinds, "kind", "k", []string{"charts", "git"}, "The kinds of versionstream. Possible values: "+strings.Join(versionstream.KindStrings, ", ")+".")
    92  	cmd.Flags().StringVarP(&options.Name, "name", "n", "", "The name of the versionstream to update. e.g. the name of the chart like 'jenkins-x/prow'")
    93  	cmd.Flags().StringArrayVarP(&options.Includes, "filter", "f", nil, "The name patterns to include - such as '*' for all names")
    94  	cmd.Flags().StringArrayVarP(&options.Excludes, "excludes", "x", nil, "The name patterns to exclude")
    95  	cmd.Flags().BoolVarP(&options.UpdateTektonImages, "images", "", false, "Update the tekton builder images for the Jenkins X Versions BDD tests")
    96  	AddStepCreatePrFlags(cmd, &options.StepCreatePrOptions)
    97  	return cmd
    98  }
    99  
   100  // ValidateVersionsOptions validates the common options for versionstream pr steps
   101  func (o *StepCreatePullRequestVersionsOptions) ValidateVersionsOptions() error {
   102  	if len(o.GitURLs) == 0 {
   103  		// Default in the versions repo
   104  		o.GitURLs = []string{config.DefaultVersionsURL}
   105  	}
   106  	if len(o.Kinds) == 0 {
   107  		return util.MissingOption("kind")
   108  	}
   109  	for _, kind := range o.Kinds {
   110  		if util.StringArrayIndex(versionstream.KindStrings, kind) < 0 {
   111  			return util.InvalidOption("kind", kind, versionstream.KindStrings)
   112  		}
   113  	}
   114  
   115  	if o.UpdateTektonImages && o.Name == "" && len(o.Includes) == 0 && util.StringArraysEqual(o.Kinds, []string{"charts", "git"}) {
   116  		// Allow for just running jx step create pr versions --images
   117  		o.Kinds = make([]string, 0)
   118  	} else if len(o.Includes) == 0 && !o.UpdateTektonImages {
   119  		if o.Name == "" && !o.UpdateTektonImages {
   120  			return util.MissingOption("name")
   121  		}
   122  	}
   123  	return nil
   124  }
   125  
   126  // Run implements this command
   127  func (o *StepCreatePullRequestVersionsOptions) Run() error {
   128  	if err := o.ValidateVersionsOptions(); err != nil {
   129  		return errors.WithStack(err)
   130  	}
   131  
   132  	modifyFns := make([]operations.ChangeFilesFn, 0)
   133  
   134  	if o.UpdateTektonImages {
   135  
   136  		builderImageVersion, err := findLatestBuilderImageVersion()
   137  		if err != nil {
   138  			return errors.WithStack(err)
   139  		}
   140  
   141  		log.Logger().Infof("the latest builder image version is %s\n", util.ColorInfo(builderImageVersion))
   142  		pro := operations.PullRequestOperation{
   143  			CommonOptions: o.CommonOptions,
   144  			GitURLs:       o.GitURLs,
   145  			SrcGitURL:     "https://github.com/jenkins-x/jenkins-x-builders.git",
   146  			Base:          o.Base,
   147  			BranchName:    o.BranchName,
   148  			Version:       builderImageVersion,
   149  			DryRun:        o.DryRun,
   150  		}
   151  		authorName, authorEmail, _ := gits.EnsureUserAndEmailSetup(o.Git())
   152  		if authorName != "" && authorEmail != "" {
   153  			pro.AuthorName = authorName
   154  			pro.AuthorEmail = authorEmail
   155  		}
   156  		fn, err := operations.CreatePullRequestRegexFn(builderImageVersion, "gcr.io/jenkinsxio/builder-(?:maven|go|terraform|go-nodejs):(?P<versions>.+)", "jenkins-x*.yml")
   157  		if err != nil {
   158  			return errors.WithStack(err)
   159  		}
   160  		modifyFns = append(modifyFns, pro.WrapChangeFilesWithCommitFn("versions", fn))
   161  		modifyFns = append(modifyFns, pro.WrapChangeFilesWithCommitFn("versions", operations.CreatePullRequestBuildersFn(builderImageVersion)))
   162  		// Update the pipeline files
   163  		fn, err = operations.CreatePullRequestRegexFn(builderImageVersion, `(?m)^\s*agent:\n\s*image: gcr.io/jenkinsxio/builder-.*:(?P<version>.*)$`, "jenkins-x-*.yml")
   164  		if err != nil {
   165  			return errors.WithStack(err)
   166  		}
   167  		modifyFns = append(modifyFns, pro.WrapChangeFilesWithCommitFn("versions", fn))
   168  
   169  		// Machine learning builders have to be handled separately
   170  		mlBuilderImageVersion, err := findLatestMLBuilderImageVersion()
   171  		if err != nil {
   172  			return errors.WithStack(err)
   173  		}
   174  
   175  		log.Logger().Infof("the latest machine learning builder image version is %s\n", util.ColorInfo(mlBuilderImageVersion))
   176  		mlPro := operations.PullRequestOperation{
   177  			CommonOptions: o.CommonOptions,
   178  			GitURLs:       o.GitURLs,
   179  			SrcGitURL:     "https://github.com/jenkins-x/jenkins-x-builders-ml.git",
   180  			Base:          o.Base,
   181  			BranchName:    o.BranchName,
   182  			Version:       mlBuilderImageVersion,
   183  			DryRun:        o.DryRun,
   184  		}
   185  		if authorName != "" && authorEmail != "" {
   186  			pro.AuthorName = authorName
   187  			pro.AuthorEmail = authorEmail
   188  		}
   189  		modifyFns = append(modifyFns, mlPro.WrapChangeFilesWithCommitFn("versions", operations.CreatePullRequestMLBuildersFn(mlBuilderImageVersion)))
   190  	}
   191  	if len(o.Includes) > 0 {
   192  		for _, kind := range o.Kinds {
   193  			modifyFns = append(modifyFns, o.CreatePullRequestUpdateVersionFilesFn(o.Includes, o.Excludes, kind, o.Helm()))
   194  		}
   195  	} else {
   196  		pro := operations.PullRequestOperation{
   197  			CommonOptions: o.CommonOptions,
   198  			GitURLs:       o.GitURLs,
   199  			Base:          o.Base,
   200  			BranchName:    o.BranchName,
   201  			Version:       o.Version,
   202  			DryRun:        o.DryRun,
   203  		}
   204  		authorName, authorEmail, _ := gits.EnsureUserAndEmailSetup(o.Git())
   205  		if authorName != "" && authorEmail != "" {
   206  			pro.AuthorName = authorName
   207  			pro.AuthorEmail = authorEmail
   208  		}
   209  		vaultClient, err := o.SystemVaultClient("")
   210  		if err != nil {
   211  			vaultClient = nil
   212  		}
   213  		for _, kind := range o.Kinds {
   214  			switch kind {
   215  			case string(versionstream.KindChart):
   216  				modifyFns = append(modifyFns, pro.WrapChangeFilesWithCommitFn("versions", operations.CreateChartChangeFilesFn(o.Name, o.Version, kind, &pro, o.Helm(), vaultClient, o.GetIOFileHandles())))
   217  			}
   218  
   219  		}
   220  	}
   221  
   222  	o.SrcGitURL = ""    // there is no src url for the overall PR
   223  	o.SkipCommit = true // As we've done all the commits already
   224  	return o.CreatePullRequest("versionstream", func(dir string, gitInfo *gits.GitRepository) ([]string, error) {
   225  		for _, kind := range o.Kinds {
   226  			if versionstream.VersionKind(kind) == versionstream.KindChart {
   227  				err := o.HelmInitDependency(dir, o.DefaultReleaseCharts())
   228  				if err != nil {
   229  					return nil, errors.Wrap(err, "failed to ensure the helm repositories were setup")
   230  				}
   231  				log.Logger().Info("updating helm repositories to find the latest chart versions...\n")
   232  				err = o.Helm().UpdateRepo()
   233  				if err != nil {
   234  					return nil, errors.Wrap(err, "failed to update helm repos")
   235  				}
   236  			}
   237  		}
   238  		for _, fn := range modifyFns {
   239  			// in a versions PR there is no overall "old version" - it's done commit by commit
   240  			_, err := fn(dir, gitInfo)
   241  			if err != nil {
   242  				return nil, errors.WithStack(err)
   243  			}
   244  		}
   245  		return nil, nil
   246  	})
   247  }
   248  
   249  func findLatestBuilderImageVersion() (string, error) {
   250  	return findLatestImageVersion("gcr.io/jenkinsxio/builder-maven")
   251  }
   252  
   253  func findLatestMLBuilderImageVersion() (string, error) {
   254  	return findLatestImageVersion("gcr.io/jenkinsxio/builder-machine-learning")
   255  }
   256  
   257  func findLatestImageVersion(image string) (string, error) {
   258  	cmd := util.Command{
   259  		Name: "gcloud",
   260  		Args: []string{
   261  			"container", "images", "list-tags", image, "--format", "json",
   262  		},
   263  	}
   264  	output, err := cmd.RunWithoutRetry()
   265  	if err != nil {
   266  		return "", errors.WithStack(err)
   267  	}
   268  	return gke.FindLatestImageTag(output)
   269  }
   270  
   271  //CreatePullRequestUpdateVersionFilesFn creates the ChangeFilesFn for directory tree of stable version files, applying the includes and excludes
   272  func (o *StepCreatePullRequestVersionsOptions) CreatePullRequestUpdateVersionFilesFn(includes []string, excludes []string, kindStr string, helmer helm.Helmer) operations.ChangeFilesFn {
   273  
   274  	return func(dir string, gitInfo *gits.GitRepository) (i []string, e error) {
   275  		answer := make([]string, 0)
   276  
   277  		kindDir := filepath.Join(dir, kindStr)
   278  		glob := filepath.Join(kindDir, "*", "*.yml")
   279  		paths, err := filepath.Glob(glob)
   280  		if err != nil {
   281  			return nil, errors.Wrapf(err, "bad glob pattern %s", glob)
   282  		}
   283  		glob = filepath.Join(kindDir, "*", "*", "*.yml")
   284  		morePaths, err := filepath.Glob(glob)
   285  		if err != nil {
   286  			return nil, errors.Wrapf(err, "bad glob pattern %s", glob)
   287  		}
   288  		paths = append(paths, morePaths...)
   289  		for _, path := range paths {
   290  			name, err := versionstream.NameFromPath(kindDir, path)
   291  			if err != nil {
   292  				return nil, errors.WithStack(err)
   293  			}
   294  			if !util.StringMatchesAny(name, includes, excludes) {
   295  				continue
   296  			} else {
   297  				pro := operations.PullRequestOperation{
   298  					CommonOptions: o.CommonOptions,
   299  					GitURLs:       o.GitURLs,
   300  					Base:          o.Base,
   301  					BranchName:    o.BranchName,
   302  					DryRun:        o.DryRun,
   303  				}
   304  				authorName, authorEmail, err := gits.EnsureUserAndEmailSetup(o.Git())
   305  				if err != nil {
   306  					pro.AuthorName = authorName
   307  					pro.AuthorEmail = authorEmail
   308  				}
   309  				var cff operations.ChangeFilesFn
   310  				switch kindStr {
   311  				case string(versionstream.KindChart):
   312  
   313  					vaultClient, err := o.SystemVaultClient("")
   314  					if err != nil {
   315  						vaultClient = nil
   316  					}
   317  					cff = pro.WrapChangeFilesWithCommitFn(kindStr, operations.CreateChartChangeFilesFn(name, "", kindStr, &pro, o.Helm(), vaultClient, o.GetIOFileHandles()))
   318  				case string(versionstream.KindGit):
   319  					cff = pro.WrapChangeFilesWithCommitFn(kindStr, pro.CreatePullRequestGitReleasesFn(name))
   320  				}
   321  				a, err := cff(dir, gitInfo)
   322  				if err != nil {
   323  					if isFailedToFindLatest(err) {
   324  						log.Logger().Warnf("Failed to find latest for %s", util.ColorInfo(name))
   325  					} else {
   326  						return nil, errors.WithStack(err)
   327  					}
   328  				}
   329  				answer = append(answer, a...)
   330  			}
   331  		}
   332  		return answer, nil
   333  	}
   334  }
   335  
   336  func isFailedToFindLatest(err error) bool {
   337  	if err == nil {
   338  		return false
   339  	}
   340  	return strings.Contains(err.Error(), "failed to find latest version for ")
   341  }