feat: generate User-Agent for DNS API clients (#2293)

This commit is contained in:
Ludovic Fernandez 2024-10-07 17:35:08 +02:00 committed by GitHub
parent a6654a99c1
commit e67b8ea21b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 288 additions and 172 deletions

View file

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

View file

@ -1,7 +1,6 @@
package sender
// Code generated by 'internal/useragent'; DO NOT EDIT.
// CODE GENERATED AUTOMATICALLY
// THIS FILE MUST NOT BE EDITED BY HAND
package sender
const (
// ourUserAgent is the User-Agent of this underlying library package.

View file

@ -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())
}
`

View file

@ -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 }}"
)
`

View file

@ -7,107 +7,123 @@ 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)
return g.writeUserAgentFile(g.sourcePath, version, comment)
}
func (g *Generator) writeUserAgentFile(filename, version, comment string) error {
tmpl, err := template.New("ua").Parse(g.template)
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 {
@ -152,72 +168,3 @@ func (v visitor) Visit(n ast.Node) ast.Visitor {
}
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)
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 bumpVersion(userAgent, mode string) (string, error) {
prevVersion := strings.TrimPrefix(userAgent, "xenolf-acme/")
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)
}
}

View file

@ -0,0 +1,84 @@
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
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

@ -11,6 +11,7 @@ import (
"github.com/dnsimple/dnsimple-go/dnsimple"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
"golang.org/x/oauth2"
)
@ -77,7 +78,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken})
client := dnsimple.NewClient(oauth2.NewClient(context.Background(), ts))
client.SetUserAgent("go-acme/lego")
client.SetUserAgent(useragent.Get())
if config.BaseURL != "" {
client.BaseURL = config.BaseURL

View file

@ -11,14 +11,11 @@ import (
"time"
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
)
const defaultBaseURL = "https://usersapiv2.epik.com/v2"
// The API server don't support User-Agent starting with `go-`, then this User-Agent is different from the other implementation.
// https://github.com/go-acme/lego/issues/2268#issuecomment-2394007004
const defaultUserAgent = "goacme/lego"
// Client the Epik API client.
type Client struct {
signature string
@ -101,7 +98,7 @@ func (c Client) RemoveHostRecord(ctx context.Context, domain string, recordID st
}
func (c Client) do(req *http.Request, result any) error {
req.Header.Set("User-Agent", defaultUserAgent)
useragent.SetHeader(req.Header)
resp, err := c.HTTPClient.Do(req)
if err != nil {

View file

@ -12,6 +12,7 @@ import (
"github.com/exoscale/egoscale/v3/credentials"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
)
// Environment variables names.
@ -85,7 +86,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
credentials.NewStaticCredentials(config.APIKey, config.APISecret),
egoscale.ClientOptWithEndpoint(egoscale.Endpoint(config.Endpoint)),
egoscale.ClientOptWithHTTPClient(&http.Client{Timeout: config.HTTPTimeout}),
egoscale.ClientOptWithUserAgent("go-acme/lego"),
egoscale.ClientOptWithUserAgent(useragent.Get()),
)
if err != nil {
return nil, fmt.Errorf("exoscale: initializing client: %w", err)

View file

@ -10,6 +10,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
infoblox "github.com/infobloxopen/infoblox-go-client"
)
@ -31,10 +32,7 @@ const (
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
const (
defaultPoolConnections = 10
defaultUserAgent = "go-acme/lego"
)
const defaultPoolConnections = 10
// Config is used to configure the creation of the DNSProvider.
type Config struct {
@ -151,7 +149,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
defer func() { _ = connector.Logout() }()
objectManager := infoblox.NewObjectManager(connector, defaultUserAgent, "")
objectManager := infoblox.NewObjectManager(connector, useragent.Get(), "")
record, err := objectManager.CreateTXTRecord(dns01.UnFqdn(info.EffectiveFQDN), info.Value, uint(d.config.TTL), d.config.DNSView)
if err != nil {
@ -176,7 +174,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
defer func() { _ = connector.Logout() }()
objectManager := infoblox.NewObjectManager(connector, defaultUserAgent, "")
objectManager := infoblox.NewObjectManager(connector, useragent.Get(), "")
// gets the record's unique ref from when we created it
d.recordRefsMu.Lock()

View file

@ -0,0 +1,29 @@
// 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/4.19.2"
// 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 = "detach"
)
// 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())
}

View file

@ -11,6 +11,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
"github.com/linode/linodego"
"golang.org/x/oauth2"
)
@ -99,7 +100,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
}
client := linodego.NewClient(oauth2Client)
client.SetUserAgent("go-acme/lego https://github.com/linode/linodego")
client.SetUserAgent(useragent.Get())
return &DNSProvider{config: config, client: &client}, nil
}

View file

@ -10,6 +10,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
"github.com/ovh/go-ovh/ovh"
)
@ -271,7 +272,7 @@ func newClient(config *Config) (*ovh.Client, error) {
return nil, fmt.Errorf("new client: %w", err)
}
client.UserAgent = "go-acme/lego"
client.UserAgent = useragent.Get()
return client, nil
}

View file

@ -9,6 +9,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
client "github.com/sacloud/api-client-go"
"github.com/sacloud/iaas-api-go"
"github.com/sacloud/iaas-api-go/helper/api"
@ -95,7 +96,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
AccessToken: config.Token,
AccessTokenSecret: config.Secret,
HttpClient: config.HTTPClient,
UserAgent: fmt.Sprintf("go-acme/lego %s", iaas.DefaultUserAgent),
UserAgent: fmt.Sprintf("%s %s", iaas.DefaultUserAgent, useragent.Get()),
},
}

View file

@ -11,6 +11,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
scwdomain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
@ -100,7 +101,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
configuration := []scw.ClientOption{
scw.WithAuth(config.AccessKey, config.Token),
scw.WithUserAgent("Scaleway Lego's provider"),
scw.WithUserAgent(useragent.Get()),
}
if config.ProjectID != "" {

View file

@ -11,6 +11,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
selectelapi "github.com/selectel/domains-go/pkg/v2"
"github.com/selectel/go-selvpcclient/v3/selvpcclient"
)
@ -25,8 +26,6 @@ const (
defaultHTTPTimeout = 30 * time.Second
)
const defaultUserAgent = "go-acme/lego"
const (
envNamespace = "SELECTELV2_"
@ -114,7 +113,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
}
headers := http.Header{}
headers.Set("User-Agent", defaultUserAgent)
useragent.SetHeader(headers)
return &DNSProvider{
baseClient: selectelapi.NewClient(config.BaseURL, config.HTTPClient, headers),

View file

@ -8,6 +8,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
"github.com/ultradns/ultradns-go-sdk/pkg/client"
"github.com/ultradns/ultradns-go-sdk/pkg/record"
"github.com/ultradns/ultradns-go-sdk/pkg/rrset"
@ -24,12 +25,10 @@ const (
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
// Default variables names.
defaultEndpoint = "https://api.ultradns.com/"
defaultUserAgent = "go-acme/lego"
)
const defaultEndpoint = "https://api.ultradns.com/"
// DNSProvider implements the challenge.Provider interface.
type DNSProvider struct {
config *Config
@ -83,7 +82,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
Username: config.Username,
Password: config.Password,
HostURL: config.Endpoint,
UserAgent: defaultUserAgent,
UserAgent: useragent.Get(),
}
uClient, err := client.NewClient(ultraConfig)

View file

@ -8,6 +8,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
"github.com/vinyldns/go-vinyldns/vinyldns"
)
@ -84,7 +85,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
Host: config.Host,
UserAgent: "go-acme/lego",
UserAgent: useragent.Get(),
})
client.HTTPClient.Timeout = 30 * time.Second