github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/step/step_tag.go (about) 1 package step 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 12 "github.com/jenkins-x/jx/v2/pkg/cmd/opts/step" 13 14 "github.com/jenkins-x/jx/v2/pkg/cmd/helper" 15 "github.com/jenkins-x/jx/v2/pkg/kube/naming" 16 17 "github.com/jenkins-x/jx-logging/pkg/log" 18 "github.com/jenkins-x/jx/v2/pkg/cmd/opts" 19 "github.com/jenkins-x/jx/v2/pkg/cmd/templates" 20 "github.com/jenkins-x/jx/v2/pkg/config" 21 "github.com/jenkins-x/jx/v2/pkg/util" 22 "github.com/spf13/cobra" 23 "k8s.io/helm/pkg/chartutil" 24 ) 25 26 const ( 27 VERSION = "version" 28 29 defaultVersionFile = "VERSION" 30 31 ValuesYamlRepositoryPrefix = " repository:" 32 ValuesYamlTagPrefix = " tag:" 33 ) 34 35 // CreateClusterOptions the flags for running create cluster 36 type StepTagOptions struct { 37 step.StepOptions 38 39 Flags StepTagFlags 40 } 41 42 type StepTagFlags struct { 43 Version string 44 VersionFile string 45 Dir string 46 ChartsDir string 47 ChartValueRepository string 48 NoApply bool 49 } 50 51 var ( 52 stepTagLong = templates.LongDesc(` 53 This pipeline step command creates a git tag using a version number prefixed with 'v' and pushes it to a 54 remote origin repo. 55 56 This commands effectively runs: 57 58 $ git commit -a -m "release $(VERSION)" --allow-empty 59 $ git tag -fa v$(VERSION) -m "Release version $(VERSION)" 60 $ git push origin v$(VERSION) 61 62 `) 63 64 stepTagExample = templates.Examples(` 65 66 jx step tag --version 1.0.0 67 68 `) 69 ) 70 71 func NewCmdStepTag(commonOpts *opts.CommonOptions) *cobra.Command { 72 options := StepTagOptions{ 73 StepOptions: step.StepOptions{ 74 CommonOptions: commonOpts, 75 }, 76 } 77 cmd := &cobra.Command{ 78 Use: "tag", 79 Short: "Creates a git tag and pushes to remote repo", 80 Long: stepTagLong, 81 Example: stepTagExample, 82 Run: func(cmd *cobra.Command, args []string) { 83 options.Cmd = cmd 84 options.Args = args 85 err := options.Run() 86 helper.CheckErr(err) 87 }, 88 } 89 90 cmd.Flags().StringVarP(&options.Flags.Version, VERSION, "v", "", "version number for the tag [required]") 91 cmd.Flags().StringVarP(&options.Flags.VersionFile, "version-file", "", defaultVersionFile, "The file name used to load the version number from if no '--version' option is specified") 92 93 cmd.Flags().StringVarP(&options.Flags.ChartsDir, "charts-dir", "d", "", "the directory of the chart to update the version") 94 cmd.Flags().StringVarP(&options.Flags.Dir, "dir", "", "", "the directory which may contain a 'jenkins-x.yml'") 95 cmd.Flags().StringVarP(&options.Flags.ChartValueRepository, "charts-value-repository", "r", "", "the fully qualified image name without the version tag. e.g. 'dockerregistry/myorg/myapp'") 96 97 cmd.Flags().BoolVarP(&options.Flags.NoApply, "no-apply", "", false, "Do not push the tag to the server, this is used for example in dry runs") 98 99 return cmd 100 } 101 102 func (o *StepTagOptions) Run() error { 103 if o.Flags.Version == "" { 104 // lets see if its defined in the VERSION file 105 path := o.Flags.VersionFile 106 if path == "" { 107 path = "VERSION" 108 } 109 exists, err := util.FileExists(path) 110 if exists && err == nil { 111 data, err := ioutil.ReadFile(path) 112 if err != nil { 113 return err 114 } 115 o.Flags.Version = strings.TrimSpace(string(data)) 116 } 117 } 118 if o.Flags.Version == "" { 119 return errors.New("No version flag") 120 } 121 log.Logger().Debug("looking for charts folder...") 122 chartsDir := o.Flags.ChartsDir 123 if chartsDir == "" { 124 exists, err := util.FileExists("Chart.yaml") 125 if !exists && err == nil { 126 // lets try find the charts/foo dir ignoring the charts/preview dir 127 chartsDir, err = o.findChartsDir() 128 if err != nil { 129 return err 130 } 131 } 132 } 133 log.Logger().Debugf("updating chart if it exists") 134 err := o.updateChart(o.Flags.Version, chartsDir) 135 if err != nil { 136 return err 137 } 138 err = o.updateChartValues(o.Flags.Version, chartsDir) 139 if err != nil { 140 return err 141 } 142 143 tag := "v" + o.Flags.Version 144 log.Logger().Debugf("performing git commit") 145 err = o.Git().AddCommit("", fmt.Sprintf("release %s", o.Flags.Version)) 146 if err != nil { 147 return err 148 } 149 150 err = o.Git().CreateTag("", tag, fmt.Sprintf("release %s", o.Flags.Version)) 151 if err != nil { 152 return err 153 } 154 155 if o.Flags.NoApply { 156 log.Logger().Infof("NoApply: no push tag to git server") 157 } else { 158 159 log.Logger().Debugf("pushing git tag %s", tag) 160 err = o.Git().PushTag("", tag) 161 if err != nil { 162 return err 163 } 164 165 log.Logger().Infof("Tag %s created and pushed to remote origin", tag) 166 } 167 return nil 168 } 169 170 func (o *StepTagOptions) updateChart(version string, chartsDir string) error { 171 chartFile := filepath.Join(chartsDir, "Chart.yaml") 172 173 exists, err := util.FileExists(chartFile) 174 if err != nil { 175 return err 176 } 177 if !exists { 178 return nil 179 } 180 chart, err := chartutil.LoadChartfile(chartFile) 181 if err != nil { 182 return err 183 } 184 if chart.Version == version { 185 return nil 186 } 187 chart.Version = version 188 chart.AppVersion = version 189 log.Logger().Infof("Updating chart version in %s to %s", chartFile, version) 190 err = chartutil.SaveChartfile(chartFile, chart) 191 if err != nil { 192 return fmt.Errorf("Failed to save chart %s: %s", chartFile, err) 193 } 194 return nil 195 } 196 197 func (o *StepTagOptions) updateChartValues(version string, chartsDir string) error { 198 valuesFile := filepath.Join(chartsDir, "values.yaml") 199 200 exists, err := util.FileExists(valuesFile) 201 if err != nil { 202 return err 203 } 204 if !exists { 205 return nil 206 } 207 data, err := ioutil.ReadFile(valuesFile) 208 lines := strings.Split(string(data), "\n") 209 chartValueRepository := o.Flags.ChartValueRepository 210 if chartValueRepository == "" { 211 chartValueRepository = o.defaultChartValueRepository() 212 } 213 updated := false 214 changedRepository := false 215 changedTag := false 216 for idx, line := range lines { 217 if chartValueRepository != "" && strings.HasPrefix(line, ValuesYamlRepositoryPrefix) && !changedRepository { 218 // lets ensure we use a valid docker image name 219 chartValueRepository = naming.ToValidImageName(chartValueRepository) 220 updated = true 221 changedRepository = true 222 log.Logger().Infof("Updating repository in %s to %s", valuesFile, chartValueRepository) 223 lines[idx] = ValuesYamlRepositoryPrefix + " " + chartValueRepository 224 } else if strings.HasPrefix(line, ValuesYamlTagPrefix) && !changedTag { 225 version = naming.ToValidImageVersion(version) 226 updated = true 227 changedTag = true 228 log.Logger().Infof("Updating tag in %s to %s", valuesFile, version) 229 lines[idx] = ValuesYamlTagPrefix + " " + version 230 } 231 } 232 if updated { 233 err = ioutil.WriteFile(valuesFile, []byte(strings.Join(lines, "\n")), util.DefaultWritePermissions) 234 if err != nil { 235 return fmt.Errorf("Failed to save chart file %s: %s", valuesFile, err) 236 } 237 } 238 return nil 239 } 240 241 func (o *StepTagOptions) defaultChartValueRepository() string { 242 gitInfo, err := o.FindGitInfo(o.Flags.ChartsDir) 243 if err != nil { 244 log.Logger().Warnf("failed to find git repository: %s", err.Error()) 245 } 246 247 projectConfig, _, _ := config.LoadProjectConfig(o.Flags.Dir) 248 dockerRegistry := o.GetDockerRegistry(projectConfig) 249 dockerRegistryOrg := o.GetDockerRegistryOrg(projectConfig, gitInfo) 250 if dockerRegistryOrg == "" { 251 dockerRegistryOrg = os.Getenv("ORG") 252 } 253 if dockerRegistryOrg == "" { 254 dockerRegistryOrg = os.Getenv("REPO_OWNER") 255 } 256 appName := os.Getenv("APP_NAME") 257 if appName == "" { 258 appName = os.Getenv("REPO_NAME") 259 } 260 if dockerRegistryOrg == "" && gitInfo != nil { 261 dockerRegistryOrg = gitInfo.Organisation 262 } 263 if appName == "" && gitInfo != nil { 264 appName = gitInfo.Name 265 } 266 if dockerRegistry != "" && dockerRegistryOrg != "" && appName != "" { 267 return dockerRegistry + "/" + dockerRegistryOrg + "/" + naming.ToValidName(appName) 268 } 269 log.Logger().Warnf("could not generate chart repository name for GetDockerRegistry %s, GetDockerRegistryOrg %s, appName %s", dockerRegistry, dockerRegistryOrg, appName) 270 return "" 271 } 272 273 // lets try find the charts dir 274 func (o *StepTagOptions) findChartsDir() (string, error) { 275 files, err := filepath.Glob("*/*/Chart.yaml") 276 if err != nil { 277 return "", fmt.Errorf("failed to find Chart.yaml file: %s", err) 278 } 279 // get the app name as expeted in a file name 280 appName := os.Getenv("APP_NAME") 281 if appName == "" { 282 appName = os.Getenv("REPO_NAME") 283 } 284 if appName != "" { 285 appName = naming.ToValidName(appName) 286 } 287 // sort files by likehood 288 rank := func(file string) int { 289 paths := strings.Split(file, string(os.PathSeparator)) 290 //not a candidate 291 if len(paths) < 3 || paths[len(paths)-2] == "preview" { 292 return 10 293 } 294 r := 3 295 // 2 points for being in charts/ directory 296 if paths[len(paths)-3] == "charts" { 297 r -= 2 298 } 299 // 1 point for being in <appName>/ directory 300 if appName != "" && paths[len(paths)-2] == appName { 301 r -= 1 302 } 303 log.Logger().Debugf("ranked %s at %d", file, r) 304 return r 305 } 306 less := func(i, j int) bool { 307 return rank(files[i]) < rank(files[j]) 308 } 309 sort.Slice(files, less) 310 // check the most likely file 311 if len(files) > 0 { 312 file := files[0] 313 paths := strings.Split(file, string(os.PathSeparator)) 314 if len(paths) >= 3 && paths[len(paths)-2] != "preview" { 315 dir, _ := filepath.Split(file) 316 return dir, nil 317 } 318 } 319 return "", nil 320 }