Skip to content

Commit

Permalink
feat: generate User-Agent for DNS API clients (#2293)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez authored Oct 7, 2024
1 parent a6654a9 commit e67b8ea
Show file tree
Hide file tree
Showing 18 changed files with 278 additions and 162 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ checks:
.PHONY: patch minor major detach

patch:
go run internal/release.go release -m patch
go run ./internal/useragent/ release -m patch

minor:
go run internal/release.go release -m minor
go run ./internal/useragent/ release -m minor

major:
go run internal/release.go release -m major
go run ./internal/useragent/ release -m major

detach:
go run internal/release.go detach
go run ./internal/useragent/ detach

# Docs
.PHONY: docs-build docs-serve docs-themes
Expand Down
5 changes: 2 additions & 3 deletions acme/api/internal/sender/useragent.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions internal/useragent/data_dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

const dnsBaseUserAgent = "goacme-lego/"

const dnsSourceFile = "./providers/dns/internal/useragent/useragent.go"

const dnsTemplate = `// Code generated by 'internal/useragent'; DO NOT EDIT.
package useragent
import (
"fmt"
"net/http"
"runtime"
)
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "goacme-lego/{{ .version }}"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
// NOTE: Update this with each tagged release.
ourUserAgentComment = "{{ .comment }}"
)
// Get builds and returns the User-Agent string.
func Get() string {
return fmt.Sprintf("%s (%s; %s; %s)", ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
}
// SetHeader sets the User-Agent header.
func SetHeader(h http.Header) {
h.Set("User-Agent", Get())
}
`
21 changes: 21 additions & 0 deletions internal/useragent/data_sender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

const senderBaseUserAgent = "xenolf-acme/"

const senderSourceFile = "./acme/api/internal/sender/useragent.go"

const senderTemplate = `// Code generated by 'internal/useragent'; DO NOT EDIT.
package sender
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/{{ .version }}"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
// NOTE: Update this with each tagged release.
ourUserAgentComment = "{{ .comment }}"
)
`
205 changes: 76 additions & 129 deletions internal/release.go → internal/useragent/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,167 +7,58 @@ import (
"go/format"
"go/parser"
"go/token"
"log"
"os"
"regexp"
"strconv"
"strings"
"text/template"

"github.com/urfave/cli/v2"
)

const sourceFile = "./acme/api/internal/sender/useragent.go"

const uaTemplate = `package sender
// CODE GENERATED AUTOMATICALLY
// THIS FILE MUST NOT BE EDITED BY HAND
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/{{ .version }}"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
// NOTE: Update this with each tagged release.
ourUserAgentComment = "{{ .comment }}"
)

`

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)
}
type Generator struct {
baseUserAgent string
template string
sourcePath string
}

func release(ctx *cli.Context) error {
mode := ctx.String("mode")
func NewGenerator(baseUserAgent string, tmpl string, sourcePath string) *Generator {
return &Generator{baseUserAgent: baseUserAgent, template: tmpl, sourcePath: sourcePath}
}

func (g *Generator) Release(mode string) error {
// Read file
data, err := readUserAgentFile(sourceFile)
data, err := readUserAgentFile(g.sourcePath)
if err != nil {
return err
}

// Bump version
newVersion, err := bumpVersion(data["ourUserAgent"], mode)
newVersion, err := g.bumpVersion(data["ourUserAgent"], mode)
if err != nil {
return err
}

// Write file
comment := "release" // detach|release
return writeUserAgentFile(sourceFile, newVersion, comment)

return g.writeUserAgentFile(g.sourcePath, newVersion, comment)
}

func detach(_ *cli.Context) error {
func (g *Generator) Detach() error {
// Read file
data, err := readUserAgentFile(sourceFile)
data, err := readUserAgentFile(g.sourcePath)
if err != nil {
return err
}

// Write file
version := strings.TrimPrefix(data["ourUserAgent"], "xenolf-acme/")
version := strings.TrimPrefix(data["ourUserAgent"], g.baseUserAgent)
comment := "detach"
return writeUserAgentFile(sourceFile, version, comment)
}

type visitor struct {
data map[string]string
return g.writeUserAgentFile(g.sourcePath, version, comment)
}

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 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
}

func writeUserAgentFile(filename, version, comment string) error {
tmpl, err := template.New("ua").Parse(uaTemplate)
func (g *Generator) writeUserAgentFile(filename, version, comment string) error {
tmpl, err := template.New("ua").Parse(g.template)
if err != nil {
return err
}
Expand All @@ -189,8 +80,8 @@ func writeUserAgentFile(filename, version, comment string) error {
return os.WriteFile(filename, source, 0o644)
}

func bumpVersion(userAgent, mode string) (string, error) {
prevVersion := strings.TrimPrefix(userAgent, "xenolf-acme/")
func (g *Generator) bumpVersion(userAgent, mode string) (string, error) {
prevVersion := strings.TrimPrefix(userAgent, g.baseUserAgent)

allString := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`).FindStringSubmatch(prevVersion)

Expand Down Expand Up @@ -221,3 +112,59 @@ func bumpVersion(userAgent, mode string) (string, error) {
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
}
Loading

0 comments on commit e67b8ea

Please sign in to comment.