github.com/verrazzano/verrazzano@v1.7.0/ci/tools/derive_upgrade_version.go (about)

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  package main
     4  
     5  import (
     6  	"flag"
     7  	"fmt"
     8  	"github.com/verrazzano/verrazzano/pkg/semver"
     9  	"os"
    10  	"os/exec"
    11  	"sort"
    12  	"strings"
    13  )
    14  
    15  const (
    16  	VersionForInstall             = "install-version"
    17  	InterimVersionForUpgrade      = "interim-version"
    18  	LatestVersionForCurrentBranch = "latest-version-for-branch"
    19  	VersionsGTE                   = "versions-gte"
    20  	VersionsLT                    = "versions-lt"
    21  )
    22  
    23  var (
    24  	workspace, versionType, developmentVersion string
    25  	excludeReleaseTags                         []string
    26  )
    27  
    28  func main() {
    29  
    30  	//Parse command line arguments to extract params
    31  	help := false
    32  	flag.BoolVar(&help, "help", false, "Display usage help")
    33  	flag.Parse()
    34  	if help {
    35  		printUsage()
    36  		os.Exit(0)
    37  	}
    38  	parseCliArgs(flag.Args())
    39  
    40  	//Extract release tags from git tag command.
    41  	releaseTags := getReleaseTags(workspace, excludeReleaseTags)
    42  	switch versionType {
    43  	case InterimVersionForUpgrade:
    44  		interimRelease := getInterimRelease(releaseTags)
    45  		fmt.Print(interimRelease)
    46  	case VersionForInstall:
    47  		installRelease := getInstallRelease(releaseTags)
    48  		fmt.Print(installRelease)
    49  	case LatestVersionForCurrentBranch:
    50  		latestRelease := getLatestReleaseForCurrentBranch(releaseTags)
    51  		fmt.Println(latestRelease)
    52  	case VersionsGTE:
    53  		tagsAfter, err := getTagsGTE(releaseTags, excludeReleaseTags[0])
    54  		if err != nil {
    55  			panic(err)
    56  		}
    57  		fmt.Println(tagsAfter)
    58  	case VersionsLT:
    59  		tagsBefore, err := getTagsLT(releaseTags, excludeReleaseTags[0])
    60  		if err != nil {
    61  			panic(err)
    62  		}
    63  		fmt.Println(tagsBefore)
    64  	default:
    65  		fmt.Printf("invalid command line argument for derive version type \n")
    66  		os.Exit(1)
    67  	}
    68  }
    69  
    70  func parseCliArgs(args []string) {
    71  
    72  	if len(args) < 1 {
    73  		fmt.Printf("\nno command line arguments were specified\n")
    74  		printUsage()
    75  		os.Exit(1)
    76  	}
    77  
    78  	if len(args) > 0 {
    79  		// Receive working directory as a command line argument.
    80  		workspace = args[0]
    81  		// Receive version type such as interimVersionForUpgrade or versionForInstall argument
    82  		versionType = args[1]
    83  	} else {
    84  		fmt.Printf("no worspace path and version type line arguments were specified\n")
    85  		os.Exit(1)
    86  	}
    87  
    88  	if len(args) > 2 {
    89  		for index, arg := range args {
    90  			// Remove any ',' or ']' suffixes and remove any '[' prefix
    91  			trimArg := strings.TrimPrefix(strings.TrimSuffix(strings.TrimSuffix(arg, ","), "]"), "[")
    92  			if index > 1 {
    93  				if versionType == LatestVersionForCurrentBranch {
    94  					developmentVersion = trimArg
    95  					return
    96  				}
    97  				excludeReleaseTags = append(excludeReleaseTags, trimArg)
    98  			}
    99  		}
   100  	}
   101  }
   102  
   103  func getReleaseTags(workspace string, excludeReleaseTags []string) []string {
   104  	// Change the working directory to the verrazzano workspace
   105  	err := os.Chdir(workspace)
   106  	if err != nil {
   107  		fmt.Printf("\nunable to change the current working directory %v", err.Error())
   108  	}
   109  	// Execute git tag command.
   110  	cmd := exec.Command("git", "tag")
   111  	out, err := cmd.Output()
   112  	if err != nil {
   113  		fmt.Printf("\nunable to execute git tag command %v", err.Error())
   114  	}
   115  
   116  	// Split the output by newline and store it in a slice
   117  	gitTags := strings.Split(string(out), "\n")
   118  
   119  	// Extract release tags from gitTags
   120  	var releaseTags []string
   121  
   122  	for _, tag := range gitTags {
   123  		if strings.HasPrefix(tag, "v") && !strings.HasPrefix(tag, "v0") {
   124  			// Exclude the release tags if tag exists in excludeReleaseTags
   125  			if !DoesTagExistsInExcludeList(tag, excludeReleaseTags) {
   126  				releaseTags = append(releaseTags, tag)
   127  			}
   128  		}
   129  	}
   130  	return releaseTags
   131  }
   132  
   133  // DoesTagExistsInExcludeList returns true if the tag exists in excludeReleasetag
   134  func DoesTagExistsInExcludeList(releaseTag string, excludeReleaseTags []string) bool {
   135  	majorMinorReleaseTag := removePatchVersion(releaseTag)
   136  	builder := strings.Builder{}
   137  	if !strings.HasPrefix(majorMinorReleaseTag, strings.ToLower("v")) {
   138  		builder.WriteString("v" + majorMinorReleaseTag)
   139  		majorMinorReleaseTag = builder.String()
   140  	}
   141  	for _, excludeTag := range excludeReleaseTags {
   142  		builder.Reset()
   143  		majorMinorExcludeTag := removePatchVersion(excludeTag)
   144  		if !strings.HasPrefix(majorMinorExcludeTag, strings.ToLower("v")) {
   145  			builder.WriteString("v" + majorMinorExcludeTag)
   146  			majorMinorExcludeTag = builder.String()
   147  		}
   148  		if majorMinorExcludeTag == majorMinorReleaseTag {
   149  			return true
   150  		}
   151  	}
   152  	return false
   153  }
   154  
   155  func getLatestReleaseForCurrentBranch(releaseTags []string) string {
   156  
   157  	builder := strings.Builder{}
   158  	var latestForCurrentBranch *semver.SemVersion
   159  
   160  	o, err := semver.NewSemVersion(developmentVersion)
   161  	o.Patch = 0
   162  	if err != nil {
   163  		return ""
   164  	}
   165  	for _, tag := range releaseTags {
   166  		var t = tag
   167  		tagVersion, err := semver.NewSemVersion(t)
   168  		if err != nil {
   169  			return ""
   170  		}
   171  		if tagVersion.IsLessThan(o) {
   172  			latestForCurrentBranch = tagVersion
   173  		}
   174  	}
   175  	builder.WriteString("v" + latestForCurrentBranch.ToString())
   176  
   177  	return builder.String()
   178  }
   179  
   180  // Derives interim version which is the latest git release tag - 1 minor version.
   181  // If there are only two unique minor versions then latest patch is derived.
   182  func getInterimRelease(releaseTags []string) string {
   183  	minorAndPatchesVersionMap, uniqueMinorVersions := getUniqueMajorMinorAndPatchVersionMap(releaseTags)
   184  	uniqueMinorReleaseCount := len(uniqueMinorVersions)
   185  
   186  	// Handles edge cases such as having less than 2 minor releases.
   187  	var interimRelease string
   188  	var installReleasePatchVersions []string
   189  	if uniqueMinorReleaseCount == 1 {
   190  		installReleasePatchVersions = minorAndPatchesVersionMap[uniqueMinorVersions[0]]
   191  		interimRelease = installReleasePatchVersions[len(releaseTags)-1]
   192  	} else if uniqueMinorReleaseCount == 2 {
   193  		secondLatestRelease := uniqueMinorVersions[len(uniqueMinorVersions)-2]
   194  		installReleasePatchVersions = minorAndPatchesVersionMap[secondLatestRelease]
   195  		interimRelease = installReleasePatchVersions[len(installReleasePatchVersions)-1]
   196  	} else if uniqueMinorReleaseCount > 2 {
   197  		secondLatestRelease := uniqueMinorVersions[len(uniqueMinorVersions)-2]
   198  		installReleasePatchVersions = minorAndPatchesVersionMap[secondLatestRelease]
   199  		interimRelease = installReleasePatchVersions[len(installReleasePatchVersions)-1]
   200  	}
   201  	return interimRelease
   202  }
   203  
   204  // Derives install version which is the latest git release tag - 2 minor version.
   205  // If there are only two unique minor versions then oldest patch is derived.
   206  func getInstallRelease(releaseTags []string) string {
   207  	minorAndPatchesVersionMap, uniqueMinorVersions := getUniqueMajorMinorAndPatchVersionMap(releaseTags)
   208  	uniqueMinorReleaseCount := len(uniqueMinorVersions)
   209  
   210  	// Handles edge cases such as having less than 2 minor releases.
   211  	var installRelease string
   212  	var installReleasePatchVersions []string
   213  	if uniqueMinorReleaseCount == 1 {
   214  		installReleasePatchVersions = minorAndPatchesVersionMap[uniqueMinorVersions[0]]
   215  		installRelease = installReleasePatchVersions[0]
   216  	} else if uniqueMinorReleaseCount == 2 {
   217  		thirdLatestRelease := uniqueMinorVersions[len(uniqueMinorVersions)-2]
   218  		installReleasePatchVersions = minorAndPatchesVersionMap[thirdLatestRelease]
   219  		installRelease = installReleasePatchVersions[0]
   220  	} else if uniqueMinorReleaseCount > 2 {
   221  		thirdLatestRelease := uniqueMinorVersions[len(uniqueMinorVersions)-3]
   222  		installReleasePatchVersions = minorAndPatchesVersionMap[thirdLatestRelease]
   223  		installRelease = installReleasePatchVersions[len(installReleasePatchVersions)-1]
   224  	}
   225  	return installRelease
   226  }
   227  
   228  func getTagsLT(tags []string, oldestAllowedVersion string) (string, error) {
   229  	builder := strings.Builder{}
   230  	o, err := semver.NewSemVersion(oldestAllowedVersion)
   231  	if err != nil {
   232  		return "", err
   233  	}
   234  
   235  	for _, tag := range tags {
   236  		var t = tag
   237  		if tag[0] == 'v' || tag[0] == 'V' {
   238  			t = tag[1:]
   239  		}
   240  		tagVersion, err := semver.NewSemVersion(t)
   241  		if err != nil {
   242  			return "", err
   243  		}
   244  		if tagVersion.IsLessThan(o) {
   245  			builder.WriteString(tag)
   246  			builder.WriteString(" ")
   247  		}
   248  	}
   249  
   250  	return builder.String(), nil
   251  }
   252  
   253  func getTagsGTE(tags []string, oldestAllowedVersion string) (string, error) {
   254  	builder := strings.Builder{}
   255  
   256  	o, err := semver.NewSemVersion(oldestAllowedVersion)
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  
   261  	for _, tag := range tags {
   262  		var t = tag
   263  		if tag[0] == 'v' || tag[0] == 'V' {
   264  			t = tag[1:]
   265  		}
   266  		tagVersion, err := semver.NewSemVersion(t)
   267  		if err != nil {
   268  			return "", err
   269  		}
   270  		if tagVersion.IsGreaterThanOrEqualTo(o) {
   271  			builder.WriteString(tag)
   272  			builder.WriteString("\n")
   273  		}
   274  	}
   275  
   276  	return builder.String(), nil
   277  }
   278  
   279  func removePatchVersion(tag string) string {
   280  	split := strings.Split(tag, ".")
   281  	return strings.Join(split[:2], ".")
   282  }
   283  
   284  func compareVersions(v1, v2 string) bool {
   285  	v1Split := strings.Split(v1, ".")
   286  	v2Split := strings.Split(v2, ".")
   287  
   288  	for i := 0; i < len(v1Split) && i < len(v2Split); i++ {
   289  		var num int
   290  		v1Num, _ := fmt.Sscanf(v1Split[i], "%d", &num)
   291  		v2Num, _ := fmt.Sscanf(v2Split[i], "%d", &num)
   292  
   293  		if v1Num != v2Num {
   294  			return v1Num < v2Num
   295  		}
   296  	}
   297  	return false
   298  }
   299  
   300  func getUniqueMajorMinorAndPatchVersionMap(releaseTags []string) (map[string][]string, []string) {
   301  	// Remove patch minorVersion
   302  	majorMinorVersions := make([]string, len(releaseTags))
   303  	for i, tag := range releaseTags {
   304  		majorMinorVersions[i] = removePatchVersion(tag)
   305  	}
   306  
   307  	// Sort based on the major and minor minorVersion
   308  	sort.SliceStable(majorMinorVersions, func(i, j int) bool {
   309  		return compareVersions(majorMinorVersions[i], majorMinorVersions[j])
   310  	})
   311  
   312  	// Remove duplicates
   313  	uniqueMinorVersions := make([]string, 0)
   314  	seen := make(map[string]bool)
   315  	for _, minorVersion := range majorMinorVersions {
   316  		if !seen[minorVersion] {
   317  			seen[minorVersion] = true
   318  			uniqueMinorVersions = append(uniqueMinorVersions, minorVersion)
   319  		}
   320  	}
   321  
   322  	// Create a map of unique majorMinorVersions and related patch versions
   323  	minorAndPatchesVersionMap := make(map[string][]string)
   324  	for _, version := range majorMinorVersions {
   325  		if _, ok := minorAndPatchesVersionMap[version]; !ok {
   326  			minorAndPatchesVersionMap[version] = make([]string, 0)
   327  		}
   328  	}
   329  
   330  	for _, tag := range releaseTags {
   331  		version := removePatchVersion(tag)
   332  		minorAndPatchesVersionMap[version] = append(minorAndPatchesVersionMap[version], tag)
   333  	}
   334  
   335  	return minorAndPatchesVersionMap, uniqueMinorVersions
   336  }
   337  
   338  // printUsage Prints the help for this program
   339  func printUsage() {
   340  	usageString := `
   341  
   342  go run derive_upgrade_version.go [args] workspace version-type exclude-releases
   343  
   344  Args:
   345  	[workspace]  Uses the workspace path to retrieve the list of release tags using git tag command
   346  	[version-type]     Specify version to derive
   347  	[exclude-releases] list of release tags to exclude 
   348  Options:
   349  	--help	prints usage
   350  `
   351  	fmt.Print(usageString)
   352  }