diff --git a/.goreleaser.yml b/.goreleaser.yml index 7084db9a32..8279b19b43 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -14,9 +14,9 @@ builds: - -s -w -X main.version={{.Version}} goos: - - windows - - darwin - linux + - darwin + - windows - freebsd - openbsd - solaris diff --git a/Makefile b/Makefile index 6dfcbfa66f..28cb339082 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 7fb4a94e98..a1ad2909ec 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -1,4 +1,4 @@ -// Code generated by 'internal/useragent'; DO NOT EDIT. +// Code generated by 'internal/releaser'; DO NOT EDIT. package sender diff --git a/cmd/lego/main.go b/cmd/lego/main.go index de49869936..61a3d532a5 100644 --- a/cmd/lego/main.go +++ b/cmd/lego/main.go @@ -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) } diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go new file mode 100644 index 0000000000..d90d02e71a --- /dev/null +++ b/cmd/lego/zz_gen_version.go @@ -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 +} diff --git a/go.mod b/go.mod index dbd468c11b..a53df9e1db 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index b812a6eeb7..29fbb58cac 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/releaser/generator.go b/internal/releaser/generator.go new file mode 100644 index 0000000000..d1b3e74e1d --- /dev/null +++ b/internal/releaser/generator.go @@ -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 +} diff --git a/internal/releaser/releaser.go b/internal/releaser/releaser.go new file mode 100644 index 0000000000..6047c427cf --- /dev/null +++ b/internal/releaser/releaser.go @@ -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) + } +} diff --git a/internal/useragent/templates/dns.go.tmpl b/internal/releaser/templates/dns.go.tmpl similarity index 92% rename from internal/useragent/templates/dns.go.tmpl rename to internal/releaser/templates/dns.go.tmpl index 3419b045ee..0e5cd65d70 100644 --- a/internal/useragent/templates/dns.go.tmpl +++ b/internal/releaser/templates/dns.go.tmpl @@ -1,4 +1,4 @@ -// Code generated by 'internal/useragent'; DO NOT EDIT. +// Code generated by 'internal/releaser'; DO NOT EDIT. package useragent diff --git a/internal/useragent/templates/sender.go.tmpl b/internal/releaser/templates/sender.go.tmpl similarity index 86% rename from internal/useragent/templates/sender.go.tmpl rename to internal/releaser/templates/sender.go.tmpl index 5fcd58b876..c072538418 100644 --- a/internal/useragent/templates/sender.go.tmpl +++ b/internal/releaser/templates/sender.go.tmpl @@ -1,4 +1,4 @@ -// Code generated by 'internal/useragent'; DO NOT EDIT. +// Code generated by 'internal/releaser'; DO NOT EDIT. package sender diff --git a/internal/releaser/templates/version.go.tmpl b/internal/releaser/templates/version.go.tmpl new file mode 100644 index 0000000000..0c25120479 --- /dev/null +++ b/internal/releaser/templates/version.go.tmpl @@ -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 +} diff --git a/internal/useragent/generator.go b/internal/useragent/generator.go deleted file mode 100644 index bd41d420a6..0000000000 --- a/internal/useragent/generator.go +++ /dev/null @@ -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 -} diff --git a/internal/useragent/main.go b/internal/useragent/main.go deleted file mode 100644 index c4de3fdaaa..0000000000 --- a/internal/useragent/main.go +++ /dev/null @@ -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 -} diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 8ecbfcccba..8454c28e94 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -1,4 +1,4 @@ -// Code generated by 'internal/useragent'; DO NOT EDIT. +// Code generated by 'internal/releaser'; DO NOT EDIT. package useragent