1
0
mirror of https://github.com/go-acme/lego.git synced 2024-12-23 09:15:11 +02:00

chore: improve internal release command (#2315)

This commit is contained in:
Ludovic Fernandez 2024-10-28 14:49:24 +01:00 committed by GitHub
parent d0708fc64e
commit 4809501817
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 311 additions and 284 deletions

View File

@ -14,9 +14,9 @@ builds:
- -s -w -X main.version={{.Version}}
goos:
- windows
- darwin
- linux
- darwin
- windows
- freebsd
- openbsd
- solaris

View File

@ -39,16 +39,16 @@ checks:
.PHONY: patch minor major detach
patch:
go run ./internal/useragent/ release -m patch
go run ./internal/releaser/ release -m patch
minor:
go run ./internal/useragent/ release -m minor
go run ./internal/releaser/ release -m minor
major:
go run ./internal/useragent/ release -m major
go run ./internal/releaser/ release -m major
detach:
go run ./internal/useragent/ detach
go run ./internal/releaser/ detach
# Docs
.PHONY: docs-build docs-serve docs-themes

View File

@ -1,4 +1,4 @@
// Code generated by 'internal/useragent'; DO NOT EDIT.
// Code generated by 'internal/releaser'; DO NOT EDIT.
package sender

View File

@ -13,8 +13,6 @@ import (
"github.com/urfave/cli/v2"
)
var version = "dev"
func main() {
app := cli.NewApp()
app.Name = "lego"
@ -22,7 +20,7 @@ func main() {
app.Usage = "Let's Encrypt client written in Go"
app.EnableBashCompletion = true
app.Version = version
app.Version = getVersion()
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("lego version %s %s/%s\n", c.App.Version, runtime.GOOS, runtime.GOARCH)
}

View File

@ -0,0 +1,15 @@
// Code generated by 'internal/releaser'; DO NOT EDIT.
package main
const defaultVersion = "v4.19.2+dev-detach"
var version = ""
func getVersion() string {
if version == "" {
return defaultVersion
}
return version
}

1
go.mod
View File

@ -36,6 +36,7 @@ require (
github.com/gophercloud/gophercloud v1.14.0
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/hashicorp/go-version v1.7.0
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df
github.com/infobloxopen/infoblox-go-client v1.1.1

2
go.sum
View File

@ -454,6 +454,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=

View File

@ -0,0 +1,84 @@
package main
import (
"bytes"
"embed"
"fmt"
"go/format"
"os"
"path/filepath"
"text/template"
)
const (
dnsTemplate = "templates/dns.go.tmpl"
dnsTargetFile = "./providers/dns/internal/useragent/useragent.go"
)
const (
senderTemplate = "templates/sender.go.tmpl"
senderTargetFile = "./acme/api/internal/sender/useragent.go"
)
const (
versionTemplate = "templates/version.go.tmpl"
versionTargetFile = "./cmd/lego/zz_gen_version.go"
)
//go:embed templates
var templateFS embed.FS
type Generator struct {
templatePath string
targetFile string
}
func NewGenerator(templatePath string, targetFile string) *Generator {
return &Generator{templatePath: templatePath, targetFile: targetFile}
}
func (g *Generator) Generate(version, comment string) error {
tmpl, err := template.New(filepath.Base(g.templatePath)).ParseFS(templateFS, g.templatePath)
if err != nil {
return fmt.Errorf("parsing template (%s): %w", g.templatePath, err)
}
b := &bytes.Buffer{}
err = tmpl.Execute(b, map[string]string{
"version": version,
"comment": comment,
})
if err != nil {
return fmt.Errorf("execute template (%s): %w", g.templatePath, err)
}
source, err := format.Source(b.Bytes())
if err != nil {
return fmt.Errorf("format generated content (%s): %w", g.targetFile, err)
}
err = os.WriteFile(g.targetFile, source, 0o644)
if err != nil {
return fmt.Errorf("write file (%s): %w", g.targetFile, err)
}
return nil
}
func generate(targetVersion, comment string) error {
generators := []*Generator{
NewGenerator(dnsTemplate, dnsTargetFile),
NewGenerator(senderTemplate, senderTargetFile),
NewGenerator(versionTemplate, versionTargetFile),
}
for _, generator := range generators {
err := generator.Generate(targetVersion, comment)
if err != nil {
return fmt.Errorf("generate file(s): %w", err)
}
}
return nil
}

View File

@ -0,0 +1,183 @@
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"strconv"
hcversion "github.com/hashicorp/go-version"
"github.com/urfave/cli/v2"
)
const flgMode = "mode"
const (
modePatch = "patch"
modeMinor = "minor"
modeMajor = "major"
)
const versionSourceFile = "./cmd/lego/zz_gen_version.go"
const (
commentRelease = "release"
commentDetach = "detach"
)
func main() {
app := cli.NewApp()
app.Name = "lego-releaser"
app.Usage = "Lego releaser"
app.HelpName = "releaser"
app.Commands = []*cli.Command{
{
Name: "release",
Usage: "Update file for a release",
Action: release,
Before: func(ctx *cli.Context) error {
mode := ctx.String("mode")
switch mode {
case modePatch, modeMinor, modeMajor:
return nil
default:
return fmt.Errorf("invalid mode: %s", mode)
}
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: flgMode,
Aliases: []string{"m"},
Value: modePatch,
Usage: fmt.Sprintf("The release mode: %s|%s|%s", modePatch, modeMinor, modeMajor),
},
},
},
{
Name: "detach",
Usage: "Update file post release",
Action: detach,
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
func release(ctx *cli.Context) error {
mode := ctx.String(flgMode)
currentVersion, err := readCurrentVersion(versionSourceFile)
if err != nil {
return fmt.Errorf("read current version: %w", err)
}
nextVersion, err := bumpVersion(mode, currentVersion)
if err != nil {
return fmt.Errorf("bump version: %w", err)
}
err = generate(nextVersion, commentRelease)
if err != nil {
return err
}
return nil
}
func detach(_ *cli.Context) error {
currentVersion, err := readCurrentVersion(versionSourceFile)
if err != nil {
return fmt.Errorf("read current version: %w", err)
}
v := currentVersion.Core().String()
err = generate(v, commentDetach)
if err != nil {
return err
}
return nil
}
func readCurrentVersion(filename string) (*hcversion.Version, error) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
if err != nil {
return nil, err
}
v := visitor{data: make(map[string]string)}
ast.Walk(v, file)
current, err := hcversion.NewSemver(v.data["defaultVersion"])
if err != nil {
return nil, err
}
return current, nil
}
type visitor struct {
data map[string]string
}
func (v visitor) Visit(n ast.Node) ast.Visitor {
if n == nil {
return nil
}
switch d := n.(type) {
case *ast.GenDecl:
if d.Tok == token.CONST {
for _, spec := range d.Specs {
valueSpec, ok := spec.(*ast.ValueSpec)
if !ok {
continue
}
if len(valueSpec.Names) != 1 || len(valueSpec.Values) != 1 {
continue
}
va, ok := valueSpec.Values[0].(*ast.BasicLit)
if !ok {
continue
}
if va.Kind != token.STRING {
continue
}
s, err := strconv.Unquote(va.Value)
if err != nil {
continue
}
v.data[valueSpec.Names[0].String()] = s
}
}
default:
// noop
}
return v
}
func bumpVersion(mode string, v *hcversion.Version) (string, error) {
segments := v.Segments()
switch mode {
case modePatch:
return fmt.Sprintf("%d.%d.%d", segments[0], segments[1], segments[2]+1), nil
case modeMinor:
return fmt.Sprintf("%d.%d.0", segments[0], segments[1]+1), nil
case modeMajor:
return fmt.Sprintf("%d.0.0", segments[0]+1), nil
default:
return "", fmt.Errorf("invalid mode: %s", mode)
}
}

View File

@ -1,4 +1,4 @@
// Code generated by 'internal/useragent'; DO NOT EDIT.
// Code generated by 'internal/releaser'; DO NOT EDIT.
package useragent

View File

@ -1,4 +1,4 @@
// Code generated by 'internal/useragent'; DO NOT EDIT.
// Code generated by 'internal/releaser'; DO NOT EDIT.
package sender

View File

@ -0,0 +1,15 @@
// Code generated by 'internal/releaser'; DO NOT EDIT.
package main
const defaultVersion = "v{{ .version }}+dev{{ if .comment }}-{{ .comment }}{{end}}"
var version = ""
func getVersion() string {
if version == "" {
return defaultVersion
}
return version
}

View File

@ -1,175 +0,0 @@
package main
import (
"bytes"
"embed"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"text/template"
)
//go:embed templates
var templateFS embed.FS
type Generator struct {
baseUserAgent string
templatePath string
sourcePath string
}
func NewGenerator(baseUserAgent string, templatePath string, sourcePath string) *Generator {
return &Generator{baseUserAgent: baseUserAgent, templatePath: templatePath, sourcePath: sourcePath}
}
func (g *Generator) Release(mode string) error {
// Read file
data, err := readUserAgentFile(g.sourcePath)
if err != nil {
return err
}
// Bump version
newVersion, err := g.bumpVersion(data["ourUserAgent"], mode)
if err != nil {
return err
}
// Write file
comment := "release" // detach|release
return g.writeUserAgentFile(g.sourcePath, newVersion, comment)
}
func (g *Generator) Detach() error {
// Read file
data, err := readUserAgentFile(g.sourcePath)
if err != nil {
return err
}
// Write file
version := strings.TrimPrefix(data["ourUserAgent"], g.baseUserAgent)
comment := "detach"
return g.writeUserAgentFile(g.sourcePath, version, comment)
}
func (g *Generator) writeUserAgentFile(filename, version, comment string) error {
tmpl, err := template.New(filepath.Base(g.templatePath)).ParseFS(templateFS, g.templatePath)
if err != nil {
return err
}
b := &bytes.Buffer{}
err = tmpl.Execute(b, map[string]string{
"version": version,
"comment": comment,
})
if err != nil {
return err
}
source, err := format.Source(b.Bytes())
if err != nil {
return err
}
return os.WriteFile(filename, source, 0o644)
}
func (g *Generator) bumpVersion(userAgent, mode string) (string, error) {
prevVersion := strings.TrimPrefix(userAgent, g.baseUserAgent)
allString := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`).FindStringSubmatch(prevVersion)
if len(allString) != 4 {
return "", fmt.Errorf("invalid version format: %s", prevVersion)
}
switch mode {
case "patch":
patch, err := strconv.Atoi(allString[3])
if err != nil {
return "", err
}
return fmt.Sprintf("%s.%s.%d", allString[1], allString[2], patch+1), nil
case "minor":
minor, err := strconv.Atoi(allString[2])
if err != nil {
return "", err
}
return fmt.Sprintf("%s.%d.0", allString[1], minor+1), nil
case "major":
major, err := strconv.Atoi(allString[1])
if err != nil {
return "", err
}
return fmt.Sprintf("%d.0.0", major+1), nil
default:
return "", fmt.Errorf("invalid mode: %s", mode)
}
}
func readUserAgentFile(filename string) (map[string]string, error) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
if err != nil {
return nil, err
}
v := visitor{data: make(map[string]string)}
ast.Walk(v, file)
return v.data, nil
}
type visitor struct {
data map[string]string
}
func (v visitor) Visit(n ast.Node) ast.Visitor {
if n == nil {
return nil
}
switch d := n.(type) {
case *ast.GenDecl:
if d.Tok == token.CONST {
for _, spec := range d.Specs {
valueSpec, ok := spec.(*ast.ValueSpec)
if !ok {
continue
}
if len(valueSpec.Names) != 1 || len(valueSpec.Values) != 1 {
continue
}
va, ok := valueSpec.Values[0].(*ast.BasicLit)
if !ok {
continue
}
if va.Kind != token.STRING {
continue
}
s, err := strconv.Unquote(va.Value)
if err != nil {
continue
}
v.data[valueSpec.Names[0].String()] = s
}
}
default:
// noop
}
return v
}

View File

@ -1,96 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
const (
dnsBaseUserAgent = "goacme-lego/"
dnsSourceFile = "./providers/dns/internal/useragent/useragent.go"
dnsTemplate = "templates/dns.go.tmpl"
)
const (
senderBaseUserAgent = "xenolf-acme/"
senderSourceFile = "./acme/api/internal/sender/useragent.go"
senderTemplate = "templates/sender.go.tmpl"
)
func main() {
app := cli.NewApp()
app.Name = "lego-releaser"
app.Usage = "Lego releaser"
app.HelpName = "releaser"
app.Commands = []*cli.Command{
{
Name: "release",
Usage: "Update file for a release",
Action: release,
Before: func(ctx *cli.Context) error {
mode := ctx.String("mode")
switch mode {
case "patch", "minor", "major":
return nil
default:
return fmt.Errorf("invalid mode: %s", mode)
}
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mode",
Aliases: []string{"m"},
Value: "patch",
Usage: "The release mode: patch|minor|major",
},
},
},
{
Name: "detach",
Usage: "Update file post release",
Action: detach,
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
func release(ctx *cli.Context) error {
mode := ctx.String("mode")
generators := []*Generator{
NewGenerator(senderBaseUserAgent, senderTemplate, senderSourceFile),
NewGenerator(dnsBaseUserAgent, dnsTemplate, dnsSourceFile),
}
for _, generator := range generators {
err := generator.Release(mode)
if err != nil {
return err
}
}
return nil
}
func detach(_ *cli.Context) error {
generators := []*Generator{
NewGenerator(senderBaseUserAgent, senderTemplate, senderSourceFile),
NewGenerator(dnsBaseUserAgent, dnsTemplate, dnsSourceFile),
}
for _, generator := range generators {
err := generator.Detach()
if err != nil {
return err
}
}
return nil
}

View File

@ -1,4 +1,4 @@
// Code generated by 'internal/useragent'; DO NOT EDIT.
// Code generated by 'internal/releaser'; DO NOT EDIT.
package useragent