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 }