generated from terraform-linters/tflint-ruleset-template
-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add make release for release automation (#836)
- Loading branch information
Showing
7 changed files
with
289 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module github.com/terraform-linters/tflint-ruleset-aws/tools/release | ||
|
||
go 1.24.0 | ||
|
||
require ( | ||
github.com/google/go-github/v69 v69.2.0 | ||
github.com/hashicorp/go-version v1.7.0 | ||
golang.org/x/oauth2 v0.27.0 | ||
) | ||
|
||
require github.com/google/go-querystring v1.1.0 // indirect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE= | ||
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM= | ||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= | ||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= | ||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= | ||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= | ||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= | ||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"io" | ||
"log" | ||
"os" | ||
"os/exec" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/google/go-github/v69/github" | ||
"github.com/hashicorp/go-version" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
var token = os.Getenv("GITHUB_TOKEN") | ||
var versionRegexp = regexp.MustCompile(`^\d+\.\d+\.\d+$`) | ||
var goModRequireSDKRegexp = regexp.MustCompile(`github.com/terraform-linters/tflint-plugin-sdk v(.+)`) | ||
|
||
func main() { | ||
if err := os.Chdir("../../"); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
currentVersion := getCurrentVersion() | ||
log.Printf("current version: %s", currentVersion) | ||
|
||
newVersion := getNewVersion() | ||
log.Printf("new version: %s", newVersion) | ||
|
||
releaseNotePath := "tools/release/release-note.md" | ||
|
||
log.Println("checking requirements...") | ||
if err := checkRequirements(currentVersion, newVersion); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
log.Println("rewriting files with new version...") | ||
if err := rewriteFileWithNewVersion("project/main.go", currentVersion, newVersion); err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := rewriteFileWithNewVersion("README.md", currentVersion, newVersion); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
log.Println("generating release notes...") | ||
if err := generateReleaseNote(currentVersion, newVersion, releaseNotePath); err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := editFileInteractive(releaseNotePath); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
log.Println("installing and running tests...") | ||
if err := execCommand(os.Stdout, "make", "test"); err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := execCommand(os.Stdout, "make", "install"); err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := execCommand(os.Stdout, "make", "e2e"); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
log.Println("committing and tagging...") | ||
if err := execCommand(os.Stdout, "git", "add", "."); err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := execCommand(os.Stdout, "git", "commit", "-m", fmt.Sprintf("Bump up version to v%s", newVersion)); err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := execCommand(os.Stdout, "git", "tag", fmt.Sprintf("v%s", newVersion)); err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := execCommand(os.Stdout, "git", "push", "origin", "master", "--tags"); err != nil { | ||
log.Fatal(err) | ||
} | ||
log.Printf("pushed v%s", newVersion) | ||
} | ||
|
||
func getCurrentVersion() string { | ||
stdout := &bytes.Buffer{} | ||
if err := execCommand(stdout, "git", "describe", "--tags", "--abbrev=0"); err != nil { | ||
log.Fatal(err) | ||
} | ||
return strings.TrimPrefix(strings.TrimSpace(stdout.String()), "v") | ||
} | ||
|
||
func getNewVersion() string { | ||
reader := bufio.NewReader(os.Stdin) | ||
fmt.Print(`Enter new version (without leading "v"): `) | ||
input, err := reader.ReadString('\n') | ||
if err != nil { | ||
log.Fatal(fmt.Errorf("failed to read user input: %w", err)) | ||
} | ||
version := strings.TrimSpace(input) | ||
|
||
if !versionRegexp.MatchString(version) { | ||
log.Fatal(fmt.Errorf("invalid version: %s", version)) | ||
} | ||
return version | ||
} | ||
|
||
func checkRequirements(old string, new string) error { | ||
if token == "" { | ||
return fmt.Errorf("GITHUB_TOKEN is not set. Required to generate release notes") | ||
} | ||
|
||
if _, err := exec.LookPath("tflint"); err != nil { | ||
return fmt.Errorf("TFLint is not installed. Required to run E2E tests") | ||
} | ||
|
||
oldVersion, err := version.NewVersion(old) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse current version: %w", err) | ||
} | ||
newVersion, err := version.NewVersion(new) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse new version: %w", err) | ||
} | ||
if !newVersion.GreaterThan(oldVersion) { | ||
return fmt.Errorf("new version must be greater than current version") | ||
} | ||
|
||
if err := checkGitStatus(); err != nil { | ||
return fmt.Errorf("failed to check Git status: %w", err) | ||
} | ||
|
||
if err := checkGoModules(); err != nil { | ||
return fmt.Errorf("failed to check Go modules: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
func checkGitStatus() error { | ||
stdout := &bytes.Buffer{} | ||
if err := execCommand(stdout, "git", "status", "--porcelain"); err != nil { | ||
return err | ||
} | ||
if strings.TrimSpace(stdout.String()) != "" { | ||
return fmt.Errorf("the current working tree is dirty. Please commit or stash changes") | ||
} | ||
|
||
stdout = &bytes.Buffer{} | ||
if err := execCommand(stdout, "git", "rev-parse", "--abbrev-ref", "HEAD"); err != nil { | ||
return err | ||
} | ||
if strings.TrimSpace(stdout.String()) != "master" { | ||
return fmt.Errorf("the current branch is not master, got %s", strings.TrimSpace(stdout.String())) | ||
} | ||
|
||
stdout = &bytes.Buffer{} | ||
if err := execCommand(stdout, "git", "config", "--get", "remote.origin.url"); err != nil { | ||
return err | ||
} | ||
if !strings.Contains(strings.TrimSpace(stdout.String()), "terraform-linters/tflint-ruleset-aws") { | ||
return fmt.Errorf("remote.origin is not terraform-linters/tflint-ruleset-aws, got %s", strings.TrimSpace(stdout.String())) | ||
} | ||
return nil | ||
} | ||
|
||
func checkGoModules() error { | ||
bytes, err := os.ReadFile("go.mod") | ||
if err != nil { | ||
return fmt.Errorf("failed to read go.mod: %w", err) | ||
} | ||
content := string(bytes) | ||
|
||
matches := goModRequireSDKRegexp.FindStringSubmatch(content) | ||
if len(matches) != 2 { | ||
return fmt.Errorf(`failed to parse go.mod: did not match "%s"`, goModRequireSDKRegexp.String()) | ||
} | ||
if !versionRegexp.MatchString(matches[1]) { | ||
return fmt.Errorf(`failed to parse go.mod: SDK version "%s" is not stable`, matches[1]) | ||
} | ||
return nil | ||
} | ||
|
||
func rewriteFileWithNewVersion(path string, old string, new string) error { | ||
log.Printf("rewrite %s", path) | ||
|
||
bytes, err := os.ReadFile(path) | ||
if err != nil { | ||
return fmt.Errorf("failed to read %s: %w", path, err) | ||
} | ||
content := string(bytes) | ||
|
||
replaced := strings.ReplaceAll(content, old, new) | ||
if replaced == content { | ||
return fmt.Errorf("%s is not changed", path) | ||
} | ||
|
||
if err := os.WriteFile(path, []byte(replaced), 0644); err != nil { | ||
return fmt.Errorf("failed to write %s: %w", path, err) | ||
} | ||
return nil | ||
} | ||
|
||
func generateReleaseNote(old string, new string, savedPath string) error { | ||
tagName := fmt.Sprintf("v%s", new) | ||
previousTagName := fmt.Sprintf("v%s", old) | ||
targetCommitish := "master" | ||
|
||
client := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{ | ||
AccessToken: token, | ||
}))) | ||
|
||
note, _, err := client.Repositories.GenerateReleaseNotes( | ||
context.Background(), | ||
"terraform-linters", | ||
"tflint-ruleset-aws", | ||
&github.GenerateNotesOptions{ | ||
TagName: tagName, | ||
PreviousTagName: &previousTagName, | ||
TargetCommitish: &targetCommitish, | ||
}, | ||
) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate release notes: %w", err) | ||
} | ||
|
||
if err := os.WriteFile(savedPath, []byte(note.Body), 0644); err != nil { | ||
return fmt.Errorf("failed to write %s: %w", savedPath, err) | ||
} | ||
return err | ||
} | ||
|
||
func editFileInteractive(path string) error { | ||
editor := "vi" | ||
if e := os.Getenv("EDITOR"); e != "" { | ||
editor = e | ||
} | ||
return execShellCommand(os.Stdout, fmt.Sprintf("%s %s", editor, path)) | ||
} | ||
|
||
func execShellCommand(stdout io.Writer, command string) error { | ||
shell := "sh" | ||
if s := os.Getenv("SHELL"); s != "" { | ||
shell = s | ||
} | ||
|
||
return execCommand(stdout, shell, "-c", command) | ||
} | ||
|
||
func execCommand(stdout io.Writer, name string, args ...string) error { | ||
cmd := exec.Command(name, args...) | ||
cmd.Stdin = os.Stdin | ||
cmd.Stdout = stdout | ||
cmd.Stderr = os.Stderr | ||
|
||
if err := cmd.Run(); err != nil { | ||
commands := append([]string{name}, args...) | ||
return fmt.Errorf(`failed to exec "%s": %w`, strings.Join(commands, " "), err) | ||
} | ||
return nil | ||
} |