mirror of https://github.com/coder/coder.git
chore: add script to analyze which releases have migrations (#10823)
* chore: add script to analyze which releases have migrations
This commit is contained in:
parent
abb2c7656a
commit
20525c8b2e
|
@ -0,0 +1,86 @@
|
||||||
|
# Migration Releases
|
||||||
|
|
||||||
|
The `main.go` is a program that lists all releases and which migrations are contained with each upgrade.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
releasemigrations [--patches] [--minors] [--majors]
|
||||||
|
-after-v2
|
||||||
|
Only include releases after v2.0.0
|
||||||
|
-dir string
|
||||||
|
Migration directory (default "coderd/database/migrations")
|
||||||
|
-list
|
||||||
|
List migrations
|
||||||
|
-majors
|
||||||
|
Include major releases
|
||||||
|
-minors
|
||||||
|
Include minor releases
|
||||||
|
-patches
|
||||||
|
Include patches releases
|
||||||
|
-versions string
|
||||||
|
Comma separated list of versions to use. This skips uses git tag to find tags.
|
||||||
|
```
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Find all migrations between 2 versions
|
||||||
|
|
||||||
|
Going from 2.3.0 to 2.4.0
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go run scripts/releasemigrations/main.go --list --versions=v2.3.0,v2.4.0 11:47:00 AM
|
||||||
|
2023/11/21 11:47:09 [minor] 4 migrations added between v2.3.0 and v2.4.0
|
||||||
|
2023/11/21 11:47:09 coderd/database/migrations/000165_prevent_autostart_days.up.sql
|
||||||
|
2023/11/21 11:47:09 coderd/database/migrations/000166_template_active_version.up.sql
|
||||||
|
2023/11/21 11:47:09 coderd/database/migrations/000167_workspace_agent_api_version.up.sql
|
||||||
|
2023/11/21 11:47:09 coderd/database/migrations/000168_pg_coord_tailnet_v2_api.up.sql
|
||||||
|
2023/11/21 11:47:09 Patches: 0 (0 with migrations)
|
||||||
|
2023/11/21 11:47:09 Minors: 1 (1 with migrations)
|
||||||
|
2023/11/21 11:47:09 Majors: 0 (0 with migrations)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Looking at all patch releases after v2
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go run scripts/releasemigrations/main.go --patches --after-v2 11:47:09 AM
|
||||||
|
2023/11/21 11:48:00 [patch] No migrations added between v2.0.0 and v2.0.1
|
||||||
|
2023/11/21 11:48:00 [patch] 2 migrations added between v2.0.1 and v2.0.2
|
||||||
|
2023/11/21 11:48:00 [patch] No migrations added between v2.1.0 and v2.1.1
|
||||||
|
2023/11/21 11:48:00 [patch] No migrations added between v2.1.1 and v2.1.2
|
||||||
|
2023/11/21 11:48:00 [patch] No migrations added between v2.1.2 and v2.1.3
|
||||||
|
2023/11/21 11:48:00 [patch] 1 migrations added between v2.1.3 and v2.1.4
|
||||||
|
2023/11/21 11:48:00 [patch] 2 migrations added between v2.1.4 and v2.1.5
|
||||||
|
2023/11/21 11:48:00 [patch] 1 migrations added between v2.3.0 and v2.3.1
|
||||||
|
2023/11/21 11:48:00 [patch] 1 migrations added between v2.3.1 and v2.3.2
|
||||||
|
2023/11/21 11:48:00 [patch] 1 migrations added between v2.3.2 and v2.3.3
|
||||||
|
2023/11/21 11:48:00 Patches: 10 (6 with migrations)
|
||||||
|
2023/11/21 11:48:00 Minors: 4 (4 with migrations)
|
||||||
|
2023/11/21 11:48:00 Majors: 0 (0 with migrations)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Seeing all the noise this thing can make
|
||||||
|
|
||||||
|
This shows when every migration was introduced.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go run scripts/releasemigrations/main.go --patches --minors --majors --list
|
||||||
|
# ...
|
||||||
|
2023/11/21 11:48:31 [minor] 5 migrations added between v2.2.1 and v2.3.0
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000160_provisioner_job_status.up.sql
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000161_workspace_agent_stats_template_id_created_at_user_id_include_sessions.up.sql
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000162_workspace_automatic_updates.up.sql
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000163_external_auth_extra.up.sql
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000164_archive_template_versions.up.sql
|
||||||
|
2023/11/21 11:48:31 [patch] 1 migrations added between v2.3.0 and v2.3.1
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000165_prevent_autostart_days.up.sql
|
||||||
|
2023/11/21 11:48:31 [patch] 1 migrations added between v2.3.1 and v2.3.2
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000166_template_active_version.up.sql
|
||||||
|
2023/11/21 11:48:31 [patch] 1 migrations added between v2.3.2 and v2.3.3
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000167_workspace_agent_api_version.up.sql
|
||||||
|
2023/11/21 11:48:31 [minor] 1 migrations added between v2.3.3 and v2.4.0
|
||||||
|
2023/11/21 11:48:31 coderd/database/migrations/000168_pg_coord_tailnet_v2_api.up.sql
|
||||||
|
2023/11/21 11:48:31 Patches: 122 (55 with migrations)
|
||||||
|
2023/11/21 11:48:31 Minors: 31 (26 with migrations)
|
||||||
|
2023/11/21 11:48:31 Majors: 1 (1 with migrations)
|
||||||
|
```
|
|
@ -0,0 +1,266 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// main will print out the number of migrations added between each release.
|
||||||
|
// All upgrades are categorized as either major, minor, or patch based on semver.
|
||||||
|
//
|
||||||
|
// This isn't an exact science and is opinionated. Upgrade paths are not
|
||||||
|
// always strictly linear from release to release. Users can skip patches for
|
||||||
|
// example.
|
||||||
|
func main() {
|
||||||
|
var includePatches bool
|
||||||
|
var includeMinors bool
|
||||||
|
var includeMajors bool
|
||||||
|
var afterV2 bool
|
||||||
|
var listMigs bool
|
||||||
|
var migrationDirectory string
|
||||||
|
var versionList string
|
||||||
|
|
||||||
|
// If you only run with --patches, the upgrades that are minors are excluded.
|
||||||
|
// Example being 1.0.0 -> 1.1.0 is a minor upgrade, so it's not included.
|
||||||
|
flag.BoolVar(&includePatches, "patches", false, "Include patches releases")
|
||||||
|
flag.BoolVar(&includeMinors, "minors", false, "Include minor releases")
|
||||||
|
flag.BoolVar(&includeMajors, "majors", false, "Include major releases")
|
||||||
|
flag.StringVar(&versionList, "versions", "", "Comma separated list of versions to use. This skips uses git tag to find tags.")
|
||||||
|
flag.BoolVar(&afterV2, "after-v2", false, "Only include releases after v2.0.0")
|
||||||
|
flag.BoolVar(&listMigs, "list", false, "List migrations")
|
||||||
|
flag.StringVar(&migrationDirectory, "dir", "coderd/database/migrations", "Migration directory")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if !includePatches && !includeMinors && !includeMajors && versionList == "" {
|
||||||
|
usage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var vList []string
|
||||||
|
if versionList != "" {
|
||||||
|
// Include all for printing purposes.
|
||||||
|
includeMajors = true
|
||||||
|
includeMinors = true
|
||||||
|
includePatches = true
|
||||||
|
vList = strings.Split(versionList, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := run(Options{
|
||||||
|
VersionList: vList,
|
||||||
|
IncludePatches: includePatches,
|
||||||
|
IncludeMinors: includeMinors,
|
||||||
|
IncludeMajors: includeMajors,
|
||||||
|
AfterV2: afterV2,
|
||||||
|
ListMigrations: listMigs,
|
||||||
|
MigrationDirectory: migrationDirectory,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
_, _ = fmt.Println("Usage: releasemigrations [--patches] [--minors] [--majors] [--list]")
|
||||||
|
_, _ = fmt.Println("Choose at lease one of --patches, --minors, or --majors. You can choose all!")
|
||||||
|
_, _ = fmt.Println("Must be run from the coder repo at the root.")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
VersionList []string
|
||||||
|
IncludePatches bool
|
||||||
|
IncludeMinors bool
|
||||||
|
IncludeMajors bool
|
||||||
|
AfterV2 bool
|
||||||
|
ListMigrations bool
|
||||||
|
MigrationDirectory string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Options) Filter(tags []string) []string {
|
||||||
|
if o.AfterV2 {
|
||||||
|
for i, tag := range tags {
|
||||||
|
if tag == "v2.0.0" {
|
||||||
|
tags = tags[i:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.IncludeMajors && o.IncludeMinors && o.IncludePatches {
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := make([]string, 0, len(tags))
|
||||||
|
current := tags[0]
|
||||||
|
filtered = append(filtered, current)
|
||||||
|
for i := 1; i < len(tags); i++ {
|
||||||
|
a := current
|
||||||
|
current = tags[i]
|
||||||
|
|
||||||
|
vDiffType := versionDiff(a, tags[i])
|
||||||
|
if !o.IncludeMajors && vDiffType == "major" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !o.IncludeMinors && vDiffType == "minor" {
|
||||||
|
// This isn't perfect, but we need to include
|
||||||
|
// the first minor release for the first patch to work.
|
||||||
|
// Eg: 1.0.0 -> 1.1.0 -> 1.1.1
|
||||||
|
// If we didn't include 1.1.0, then the 1.1.1 patch would
|
||||||
|
// apply to 1.0.0
|
||||||
|
if !o.IncludePatches {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !o.IncludePatches && vDiffType == "patch" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, tags[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(opts Options) error {
|
||||||
|
var tags []string
|
||||||
|
if len(opts.VersionList) > 0 {
|
||||||
|
tags = opts.VersionList
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
tags, err = gitTags()
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("gitTags: %w", err)
|
||||||
|
}
|
||||||
|
tags = opts.Filter(tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
patches := make([]string, 0)
|
||||||
|
minors := make([]string, 0)
|
||||||
|
majors := make([]string, 0)
|
||||||
|
patchesHasMig := 0
|
||||||
|
minorsHasMig := 0
|
||||||
|
majorsHasMig := 0
|
||||||
|
|
||||||
|
for i := 0; i < len(tags)-1; i++ {
|
||||||
|
a := tags[i]
|
||||||
|
b := tags[i+1]
|
||||||
|
|
||||||
|
migrations, err := hasMigrationDiff(opts.MigrationDirectory, a, b)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("hasMigrationDiff %q->%q: %w", a, b, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vDiff := fmt.Sprintf("%s->%s", a, b)
|
||||||
|
vDiffType := versionDiff(a, b)
|
||||||
|
skipPrint := true
|
||||||
|
switch vDiffType {
|
||||||
|
case "major":
|
||||||
|
majors = append(majors, vDiff)
|
||||||
|
if len(migrations) > 0 {
|
||||||
|
majorsHasMig++
|
||||||
|
}
|
||||||
|
skipPrint = !opts.IncludeMajors
|
||||||
|
case "minor":
|
||||||
|
minors = append(minors, vDiff)
|
||||||
|
if len(migrations) > 0 {
|
||||||
|
minorsHasMig++
|
||||||
|
}
|
||||||
|
skipPrint = !opts.IncludeMinors
|
||||||
|
case "patch":
|
||||||
|
patches = append(patches, vDiff)
|
||||||
|
if len(migrations) > 0 {
|
||||||
|
patchesHasMig++
|
||||||
|
}
|
||||||
|
skipPrint = !opts.IncludePatches
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipPrint {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if migrations != nil {
|
||||||
|
log.Printf("[%s] %d migrations added between %s and %s\n", vDiffType, len(migrations), a, b)
|
||||||
|
if opts.ListMigrations {
|
||||||
|
for _, migration := range migrations {
|
||||||
|
log.Printf("\t%s", migration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[%s] No migrations added between %s and %s\n", vDiffType, a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Patches: %d (%d with migrations)\n", len(patches), patchesHasMig)
|
||||||
|
log.Printf("Minors: %d (%d with migrations)\n", len(minors), minorsHasMig)
|
||||||
|
log.Printf("Majors: %d (%d with migrations)\n", len(majors), majorsHasMig)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionDiff(a, b string) string {
|
||||||
|
ac, bc := semver.Canonical(a), semver.Canonical(b)
|
||||||
|
if semver.Major(ac) != semver.Major(bc) {
|
||||||
|
return "major"
|
||||||
|
}
|
||||||
|
if semver.MajorMinor(ac) != semver.MajorMinor(bc) {
|
||||||
|
return "minor"
|
||||||
|
}
|
||||||
|
return "patch"
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasMigrationDiff(dir string, a, b string) ([]string, error) {
|
||||||
|
cmd := exec.Command("git", "diff",
|
||||||
|
// Only added files
|
||||||
|
"--diff-filter=A",
|
||||||
|
"--name-only",
|
||||||
|
a, b, dir)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s\n%s", strings.Join(cmd.Args, " "), err)
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||||
|
filtered := make([]string, 0, len(migrations))
|
||||||
|
for _, migration := range migrations {
|
||||||
|
migration := migration
|
||||||
|
if strings.Contains(migration, "fixtures") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Only show the ups
|
||||||
|
if strings.HasSuffix(migration, ".down.sql") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, migration)
|
||||||
|
}
|
||||||
|
return filtered, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitTags() ([]string, error) {
|
||||||
|
cmd := exec.Command("git", "tag")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := strings.Split(string(output), "\n")
|
||||||
|
|
||||||
|
// Sort by semver
|
||||||
|
semver.Sort(tags)
|
||||||
|
|
||||||
|
filtered := make([]string, 0, len(tags))
|
||||||
|
for _, tag := range tags {
|
||||||
|
if tag != "" && semver.IsValid(tag) {
|
||||||
|
filtered = append(filtered, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered, nil
|
||||||
|
}
|
Loading…
Reference in New Issue