mirror of
https://github.com/go-acme/lego
synced 2026-03-14 14:35:48 +01:00
feat: hook manager
This commit is contained in:
parent
3dfdeb0ac6
commit
c174a0c257
11 changed files with 489 additions and 96 deletions
|
|
@ -61,11 +61,6 @@ func renew(ctx context.Context, cmd *cli.Command) error {
|
|||
|
||||
certsStorage := storage.NewCertificatesStorage(cmd.String(flgPath))
|
||||
|
||||
meta := map[string]string{
|
||||
// TODO(ldez) add account ID.
|
||||
hook.EnvAccountEmail: account.Email,
|
||||
}
|
||||
|
||||
lazyClient := sync.OnceValues(func() (*lego.Client, error) {
|
||||
client, err := newClient(cmd, account, keyType)
|
||||
if err != nil {
|
||||
|
|
@ -77,16 +72,18 @@ func renew(ctx context.Context, cmd *cli.Command) error {
|
|||
return client, nil
|
||||
})
|
||||
|
||||
hookManager := newHookManager(cmd, certsStorage, account)
|
||||
|
||||
// CSR
|
||||
if cmd.IsSet(flgCSR) {
|
||||
return renewForCSR(ctx, cmd, lazyClient, certsStorage, meta)
|
||||
return renewForCSR(ctx, cmd, lazyClient, certsStorage, hookManager)
|
||||
}
|
||||
|
||||
// Domains
|
||||
return renewForDomains(ctx, cmd, lazyClient, certsStorage, meta)
|
||||
return renewForDomains(ctx, cmd, lazyClient, certsStorage, hookManager)
|
||||
}
|
||||
|
||||
func renewForDomains(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp, certsStorage *storage.CertificatesStorage, meta map[string]string) error {
|
||||
func renewForDomains(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp, certsStorage *storage.CertificatesStorage, hookManager *hook.Manager) error {
|
||||
domains := cmd.StringSlice(flgDomains)
|
||||
|
||||
certID := cmd.String(flgCertName)
|
||||
|
|
@ -147,6 +144,13 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp,
|
|||
slog.Any("time-remaining", FormattableDuration(cert.NotAfter.Sub(time.Now().UTC()))),
|
||||
)
|
||||
|
||||
err = hookManager.Pre(ctx, certID, renewalDomains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pre-renew hook: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = hookManager.Post(ctx) }()
|
||||
|
||||
client, err := lazyClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("set up client: %w", err)
|
||||
|
|
@ -181,12 +185,10 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp,
|
|||
return fmt.Errorf("could not save the resource: %w", err)
|
||||
}
|
||||
|
||||
hook.AddPathToMetadata(meta, certRes, certsStorage, options)
|
||||
|
||||
return hook.Launch(ctx, cmd.String(flgDeployHook), cmd.Duration(flgDeployHookTimeout), meta)
|
||||
return hookManager.Deploy(ctx, certRes, options)
|
||||
}
|
||||
|
||||
func renewForCSR(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp, certsStorage *storage.CertificatesStorage, meta map[string]string) error {
|
||||
func renewForCSR(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp, certsStorage *storage.CertificatesStorage, hookManager *hook.Manager) error {
|
||||
csr, err := readCSRFile(cmd.String(flgCSR))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read CSR file %q: %w", cmd.String(flgCSR), err)
|
||||
|
|
@ -231,6 +233,13 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp, cert
|
|||
slog.Any("time-remaining", FormattableDuration(cert.NotAfter.Sub(time.Now().UTC()))),
|
||||
)
|
||||
|
||||
err = hookManager.Pre(ctx, certID, certcrypto.ExtractDomainsCSR(csr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("CSR: pre-renew hook: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = hookManager.Post(ctx) }()
|
||||
|
||||
client, err := lazyClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("set up client: %w", err)
|
||||
|
|
@ -256,9 +265,7 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, lazyClient lzSetUp, cert
|
|||
return fmt.Errorf("CSR: could not save the resource: %w", err)
|
||||
}
|
||||
|
||||
hook.AddPathToMetadata(meta, certRes, certsStorage, options)
|
||||
|
||||
return hook.Launch(ctx, cmd.String(flgDeployHook), cmd.Duration(flgDeployHookTimeout), meta)
|
||||
return hookManager.Deploy(ctx, certRes, options)
|
||||
}
|
||||
|
||||
func getFlagRenewDays(cmd *cli.Command) int {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ func run(ctx context.Context, cmd *cli.Command) error {
|
|||
return fmt.Errorf("set up account: %w", err)
|
||||
}
|
||||
|
||||
certsStorage := storage.NewCertificatesStorage(cmd.String(flgPath))
|
||||
|
||||
hookManager := newHookManager(cmd, certsStorage, account)
|
||||
|
||||
client, err := newClient(cmd, account, keyType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new client: %w", err)
|
||||
|
|
@ -62,7 +66,7 @@ func run(ctx context.Context, cmd *cli.Command) error {
|
|||
|
||||
setupChallenges(cmd, client)
|
||||
|
||||
certRes, err := obtainCertificate(ctx, cmd, client)
|
||||
certRes, err := obtainCertificate(ctx, cmd, client, hookManager)
|
||||
if err != nil {
|
||||
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
|
||||
// Due to us not returning partial certificate we can just exit here instead of at the end.
|
||||
|
|
@ -74,8 +78,6 @@ func run(ctx context.Context, cmd *cli.Command) error {
|
|||
certRes.ID = certID
|
||||
}
|
||||
|
||||
certsStorage := storage.NewCertificatesStorage(cmd.String(flgPath))
|
||||
|
||||
options := newSaveOptions(cmd)
|
||||
|
||||
err = certsStorage.Save(certRes, options)
|
||||
|
|
@ -83,20 +85,20 @@ func run(ctx context.Context, cmd *cli.Command) error {
|
|||
return fmt.Errorf("could not save the resource: %w", err)
|
||||
}
|
||||
|
||||
meta := map[string]string{
|
||||
// TODO(ldez) add account ID.
|
||||
hook.EnvAccountEmail: account.Email,
|
||||
}
|
||||
|
||||
hook.AddPathToMetadata(meta, certRes, certsStorage, options)
|
||||
|
||||
return hook.Launch(ctx, cmd.String(flgDeployHook), cmd.Duration(flgDeployHookTimeout), meta)
|
||||
return hookManager.Deploy(ctx, certRes, options)
|
||||
}
|
||||
|
||||
func obtainCertificate(ctx context.Context, cmd *cli.Command, client *lego.Client) (*certificate.Resource, error) {
|
||||
func obtainCertificate(ctx context.Context, cmd *cli.Command, client *lego.Client, hookManager *hook.Manager) (*certificate.Resource, error) {
|
||||
domains := cmd.StringSlice(flgDomains)
|
||||
|
||||
if len(domains) > 0 {
|
||||
err := hookManager.Pre(ctx, cmd.String(flgCertName), domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = hookManager.Post(ctx) }()
|
||||
|
||||
// obtain a certificate, generating a new private key
|
||||
request := newObtainRequest(cmd, domains)
|
||||
|
||||
|
|
@ -119,6 +121,13 @@ func obtainCertificate(ctx context.Context, cmd *cli.Command, client *lego.Clien
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = hookManager.Pre(ctx, cmd.String(flgCertName), certcrypto.ExtractDomainsCSR(csr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = hookManager.Post(ctx) }()
|
||||
|
||||
// obtain a certificate for this CSR
|
||||
request := newObtainForCSRRequest(cmd, csr)
|
||||
|
||||
|
|
|
|||
52
cmd/flags.go
52
cmd/flags.go
|
|
@ -143,8 +143,12 @@ const (
|
|||
|
||||
// Flags names related to hooks.
|
||||
const (
|
||||
flgPreHook = "pre-hook"
|
||||
flgPreHookTimeout = "pre-hook-timeout"
|
||||
flgDeployHook = "deploy-hook"
|
||||
flgDeployHookTimeout = "deploy-hook-timeout"
|
||||
flgPostHook = "post-hook"
|
||||
flgPostHookTimeout = "post-hook-timeout"
|
||||
)
|
||||
|
||||
// Flag names related to logs.
|
||||
|
|
@ -614,13 +618,31 @@ func createObtainFlags() []cli.Flag {
|
|||
}
|
||||
}
|
||||
|
||||
func createHookFlags() []cli.Flag {
|
||||
func createPreHookFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Category: categoryHooks,
|
||||
Name: flgPreHook,
|
||||
Sources: cli.EnvVars(toEnvName(flgPreHook)),
|
||||
Usage: "Define a pre-hook. This hook is runs, before the renewal, in cases where a certificate will be effectively renewed.",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Category: categoryHooks,
|
||||
Name: flgPreHookTimeout,
|
||||
Sources: cli.EnvVars(toEnvName(flgPreHookTimeout)),
|
||||
Usage: "Define the timeout for the pre-hook execution.",
|
||||
Value: 2 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createDeployHookFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Category: categoryHooks,
|
||||
Name: flgDeployHook,
|
||||
Sources: cli.EnvVars(toEnvName(flgDeployHook)),
|
||||
Usage: "Define a hook. The hook is executed only when the certificates are effectively created/renewed.",
|
||||
Usage: "Define a hook. The hook is runs, after the renewal, in cases where a certificate is successfully created/renewed.",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Category: categoryHooks,
|
||||
|
|
@ -632,6 +654,24 @@ func createHookFlags() []cli.Flag {
|
|||
}
|
||||
}
|
||||
|
||||
func createPostHookFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Category: categoryHooks,
|
||||
Name: flgPostHook,
|
||||
Sources: cli.EnvVars(toEnvName(flgPostHook)),
|
||||
Usage: "Define a post-hook. This hook runs, after the renewal, in cases where a certificate renewed, regardless of whether any errors occurred.",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Category: categoryHooks,
|
||||
Name: flgPostHookTimeout,
|
||||
Sources: cli.EnvVars(toEnvName(flgPostHookTimeout)),
|
||||
Usage: "Define the timeout for the post-hook execution.",
|
||||
Value: 2 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateLogFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -663,7 +703,9 @@ func createRunFlags() []cli.Flag {
|
|||
flags = append(flags, createAcceptFlag())
|
||||
flags = append(flags, createChallengesFlags()...)
|
||||
flags = append(flags, createObtainFlags()...)
|
||||
flags = append(flags, createHookFlags()...)
|
||||
flags = append(flags, createPreHookFlags()...)
|
||||
flags = append(flags, createDeployHookFlags()...)
|
||||
flags = append(flags, createPostHookFlags()...)
|
||||
|
||||
flags = append(flags,
|
||||
&cli.StringFlag{
|
||||
|
|
@ -688,7 +730,9 @@ func createRenewFlags() []cli.Flag {
|
|||
flags = append(flags, createStorageFlags()...)
|
||||
flags = append(flags, createChallengesFlags()...)
|
||||
flags = append(flags, createObtainFlags()...)
|
||||
flags = append(flags, createHookFlags()...)
|
||||
flags = append(flags, createPreHookFlags()...)
|
||||
flags = append(flags, createDeployHookFlags()...)
|
||||
flags = append(flags, createPostHookFlags()...)
|
||||
|
||||
flags = append(flags,
|
||||
&cli.IntFlag{
|
||||
|
|
|
|||
|
|
@ -9,29 +9,9 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v5/certificate"
|
||||
"github.com/go-acme/lego/v5/cmd/internal/storage"
|
||||
)
|
||||
|
||||
// TODO(ldez) rename the env vars with LEGO_HOOK_ prefix to avoid collisions with flag names.
|
||||
const (
|
||||
EnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
||||
EnvCertDomain = "LEGO_CERT_DOMAIN"
|
||||
EnvCertPath = "LEGO_CERT_PATH"
|
||||
EnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
||||
EnvIssuerCertKeyPath = "LEGO_ISSUER_CERT_PATH"
|
||||
EnvCertPEMPath = "LEGO_CERT_PEM_PATH"
|
||||
EnvCertPFXPath = "LEGO_CERT_PFX_PATH"
|
||||
)
|
||||
|
||||
// TODO(ldez): merge this with the previous constant block.
|
||||
const (
|
||||
EnvCertNameSanitized = "LEGO_HOOK_CERT_NAME_SANITIZED"
|
||||
EnvCertID = "LEGO_HOOK_CERT_ID"
|
||||
EnvCertDomains = "LEGO_HOOK_CERT_DOMAINS"
|
||||
)
|
||||
|
||||
// Launch executes a command.
|
||||
func Launch(ctx context.Context, hook string, timeout time.Duration, meta map[string]string) error {
|
||||
if hook == "" {
|
||||
return nil
|
||||
|
|
@ -83,36 +63,3 @@ func Launch(ctx context.Context, hook string, timeout time.Duration, meta map[st
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func metaToEnv(meta map[string]string) []string {
|
||||
var envs []string
|
||||
|
||||
for k, v := range meta {
|
||||
envs = append(envs, k+"="+v)
|
||||
}
|
||||
|
||||
return envs
|
||||
}
|
||||
|
||||
// AddPathToMetadata adds information about the certificate to the metadata map.
|
||||
func AddPathToMetadata(meta map[string]string, certRes *certificate.Resource, certsStorage *storage.CertificatesStorage, options *storage.SaveOptions) {
|
||||
meta[EnvCertID] = certRes.ID
|
||||
meta[EnvCertNameSanitized] = storage.SanitizedName(certRes.ID)
|
||||
|
||||
meta[EnvCertDomains] = strings.Join(certRes.Domains, ",")
|
||||
|
||||
meta[EnvCertPath] = certsStorage.GetFileName(certRes.ID, storage.ExtCert)
|
||||
meta[EnvCertKeyPath] = certsStorage.GetFileName(certRes.ID, storage.ExtKey)
|
||||
|
||||
if certRes.IssuerCertificate != nil {
|
||||
meta[EnvIssuerCertKeyPath] = certsStorage.GetFileName(certRes.ID, storage.ExtIssuer)
|
||||
}
|
||||
|
||||
if options.PEM {
|
||||
meta[EnvCertPEMPath] = certsStorage.GetFileName(certRes.ID, storage.ExtPEM)
|
||||
}
|
||||
|
||||
if options.PFX {
|
||||
meta[EnvCertPFXPath] = certsStorage.GetFileName(certRes.ID, storage.ExtPFX)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -60,13 +59,3 @@ func Test_Launch_errors(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_metaToEnv(t *testing.T) {
|
||||
env := metaToEnv(map[string]string{
|
||||
"foo": "bar",
|
||||
})
|
||||
|
||||
expected := []string{"foo=bar"}
|
||||
|
||||
assert.Equal(t, expected, env)
|
||||
}
|
||||
|
|
|
|||
96
cmd/internal/hook/manager.go
Normal file
96
cmd/internal/hook/manager.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v5/certificate"
|
||||
"github.com/go-acme/lego/v5/cmd/internal/storage"
|
||||
"github.com/go-acme/lego/v5/log"
|
||||
)
|
||||
|
||||
// Action represents a hook action.
|
||||
type Action struct {
|
||||
Cmd string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Manager manages hooks.
|
||||
type Manager struct {
|
||||
certsStorage *storage.CertificatesStorage
|
||||
|
||||
metadata map[string]string
|
||||
|
||||
pre *Action
|
||||
deploy *Action
|
||||
post *Action
|
||||
}
|
||||
|
||||
// NewManager creates a new hook Manager.
|
||||
func NewManager(certsStorage *storage.CertificatesStorage, options ...Option) *Manager {
|
||||
m := &Manager{
|
||||
certsStorage: certsStorage,
|
||||
metadata: make(map[string]string),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(m)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Pre runs the pre-hook if defined.
|
||||
func (h *Manager) Pre(ctx context.Context, certID string, domains []string) error {
|
||||
if h.pre == nil || h.pre.Cmd == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
addCertificateMetadata(h.metadata, certID, domains)
|
||||
|
||||
err := Launch(ctx, h.pre.Cmd, h.pre.Timeout, h.metadata)
|
||||
if err != nil {
|
||||
log.Error("Pre hook.", log.ErrorAttr(err))
|
||||
|
||||
return fmt.Errorf("pre hook: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deploy runs the deploy-hook if defined.
|
||||
func (h *Manager) Deploy(ctx context.Context, certRes *certificate.Resource, options *storage.SaveOptions) error {
|
||||
if h.deploy == nil || h.deploy.Cmd == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
addCertificateMetadata(h.metadata, certRes.ID, certRes.Domains)
|
||||
addCertificatePathsMetadata(h.metadata, certRes, h.certsStorage, options)
|
||||
|
||||
err := Launch(ctx, h.deploy.Cmd, h.deploy.Timeout, h.metadata)
|
||||
if err != nil {
|
||||
log.Error("Deploy hook.", log.ErrorAttr(err))
|
||||
|
||||
return fmt.Errorf("deploy hook: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Post runs the post-hook if defined.
|
||||
// This must be called inside a defer statement to ensure the hook is always run.
|
||||
func (h *Manager) Post(ctx context.Context) error {
|
||||
if h.post == nil || h.post.Cmd == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := Launch(ctx, h.post.Cmd, h.post.Timeout, h.metadata)
|
||||
if err != nil {
|
||||
log.Error("Post hook.", log.ErrorAttr(err))
|
||||
|
||||
return fmt.Errorf("post hook: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
62
cmd/internal/hook/manager_options.go
Normal file
62
cmd/internal/hook/manager_options.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v5/cmd/internal/storage"
|
||||
)
|
||||
|
||||
type Option func(m *Manager)
|
||||
|
||||
// WithPre sets the pre-hook.
|
||||
func WithPre(cmd string, timeout time.Duration) Option {
|
||||
return func(m *Manager) {
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
m.pre = &Action{
|
||||
Cmd: cmd,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeploy sets the deploy-hook.
|
||||
func WithDeploy(cmd string, timeout time.Duration) Option {
|
||||
return func(m *Manager) {
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
m.deploy = &Action{
|
||||
Cmd: cmd,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithPost sets the post-hook.
|
||||
func WithPost(cmd string, timeout time.Duration) Option {
|
||||
return func(m *Manager) {
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
m.post = &Action{
|
||||
Cmd: cmd,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithAccountMetadata initializes the metadata with the account data.
|
||||
func WithAccountMetadata(account *storage.Account) Option {
|
||||
return func(m *Manager) {
|
||||
if account == nil {
|
||||
return
|
||||
}
|
||||
|
||||
addAccountMetadata(m.metadata, account)
|
||||
}
|
||||
}
|
||||
144
cmd/internal/hook/manager_test.go
Normal file
144
cmd/internal/hook/manager_test.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v5/certificate"
|
||||
"github.com/go-acme/lego/v5/cmd/internal/storage"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Manager(t *testing.T) {
|
||||
certificatesStorage := storage.NewCertificatesStorage(t.TempDir())
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
options []Option
|
||||
}{
|
||||
{
|
||||
desc: "all hooks",
|
||||
options: []Option{
|
||||
WithPre("echo Pre Hook", 1*time.Second),
|
||||
WithDeploy("echo Deploy Hook", 1*time.Second),
|
||||
WithPost("echo Post Hook", 1*time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pre-hook only",
|
||||
options: []Option{
|
||||
WithPre("echo Pre Hook", 1*time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "deploy-hook only",
|
||||
options: []Option{
|
||||
WithDeploy("echo Deploy Hook", 1*time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "post-hook only",
|
||||
options: []Option{
|
||||
WithPost("echo Post Hook", 1*time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no hook",
|
||||
},
|
||||
{
|
||||
desc: "all hooks (metadata)",
|
||||
options: []Option{
|
||||
WithPre("echo Pre Hook", 1*time.Second),
|
||||
WithDeploy("echo Deploy Hook", 1*time.Second),
|
||||
WithPost("echo Post Hook", 1*time.Second),
|
||||
WithAccountMetadata(&storage.Account{ID: "foo@exmaple.com", Email: "bar@example.com"}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := NewManager(certificatesStorage, test.options...)
|
||||
|
||||
err := manager.Pre(t.Context(), "a", []string{"example.com", "example.org"})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log(manager.metadata)
|
||||
|
||||
err = manager.Deploy(t.Context(), &certificate.Resource{ID: "example.org"}, &storage.SaveOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log(manager.metadata)
|
||||
|
||||
err = manager.Post(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log(manager.metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Manager_errors(t *testing.T) {
|
||||
certificatesStorage := storage.NewCertificatesStorage(t.TempDir())
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
options []Option
|
||||
requirePre require.ErrorAssertionFunc
|
||||
requireDeploy require.ErrorAssertionFunc
|
||||
requirePost require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
desc: "pre-hook error",
|
||||
options: []Option{
|
||||
WithPre("thisappdoesnotexistpre", 1*time.Second),
|
||||
WithDeploy("echo Deploy Hook", 1*time.Second),
|
||||
WithPost("echo Post Hook", 1*time.Second),
|
||||
},
|
||||
requirePre: require.Error,
|
||||
requireDeploy: require.NoError,
|
||||
requirePost: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "deploy-hook error",
|
||||
options: []Option{
|
||||
WithPre("echo Pre Hook", 1*time.Second),
|
||||
WithDeploy("thiscommanddoesnotexistdeploy", 1*time.Second),
|
||||
WithPost("echo Post Hook", 1*time.Second),
|
||||
},
|
||||
requirePre: require.NoError,
|
||||
requireDeploy: require.Error,
|
||||
requirePost: require.NoError,
|
||||
},
|
||||
{
|
||||
desc: "post-hook error",
|
||||
options: []Option{
|
||||
WithPre("echo Pre Hook", 1*time.Second),
|
||||
WithDeploy("echo Deploy Hook", 1*time.Second),
|
||||
WithPost("thiscommanddoesnotexistpost", 1*time.Second),
|
||||
},
|
||||
requirePre: require.NoError,
|
||||
requireDeploy: require.NoError,
|
||||
requirePost: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := NewManager(certificatesStorage, test.options...)
|
||||
|
||||
err := manager.Pre(t.Context(), "a", []string{"example.com", "example.org"})
|
||||
test.requirePre(t, err)
|
||||
|
||||
err = manager.Deploy(t.Context(), &certificate.Resource{ID: "example.org"}, &storage.SaveOptions{})
|
||||
test.requireDeploy(t, err)
|
||||
|
||||
err = manager.Post(t.Context())
|
||||
test.requirePost(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
67
cmd/internal/hook/metdata.go
Normal file
67
cmd/internal/hook/metdata.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v5/certificate"
|
||||
"github.com/go-acme/lego/v5/cmd/internal/storage"
|
||||
)
|
||||
|
||||
// Metadata related to account.
|
||||
const (
|
||||
EnvAccountID = "LEGO_HOOK_ACCOUNT_ID"
|
||||
EnvAccountEmail = "LEGO_HOOK_ACCOUNT_EMAIL"
|
||||
)
|
||||
|
||||
// Metadata related to certificate.
|
||||
const (
|
||||
EnvCertName = "LEGO_HOOK_CERT_NAME"
|
||||
EnvCertNameSanitized = "LEGO_HOOK_CERT_NAME_SANITIZED"
|
||||
EnvCertDomains = "LEGO_HOOK_CERT_DOMAINS"
|
||||
EnvCertPath = "LEGO_HOOK_CERT_PATH"
|
||||
EnvCertKeyPath = "LEGO_HOOK_CERT_KEY_PATH"
|
||||
EnvIssuerCertKeyPath = "LEGO_HOOK_ISSUER_CERT_PATH"
|
||||
EnvCertPEMPath = "LEGO_HOOK_CERT_PEM_PATH"
|
||||
EnvCertPFXPath = "LEGO_HOOK_CERT_PFX_PATH"
|
||||
)
|
||||
|
||||
func addAccountMetadata(meta map[string]string, account *storage.Account) {
|
||||
meta[EnvAccountID] = account.ID
|
||||
meta[EnvAccountEmail] = account.Email
|
||||
}
|
||||
|
||||
func addCertificatePathsMetadata(meta map[string]string, certRes *certificate.Resource, certsStorage *storage.CertificatesStorage, options *storage.SaveOptions) {
|
||||
meta[EnvCertPath] = certsStorage.GetFileName(certRes.ID, storage.ExtCert)
|
||||
meta[EnvCertKeyPath] = certsStorage.GetFileName(certRes.ID, storage.ExtKey)
|
||||
|
||||
if certRes.IssuerCertificate != nil {
|
||||
meta[EnvIssuerCertKeyPath] = certsStorage.GetFileName(certRes.ID, storage.ExtIssuer)
|
||||
}
|
||||
|
||||
if options.PEM {
|
||||
meta[EnvCertPEMPath] = certsStorage.GetFileName(certRes.ID, storage.ExtPEM)
|
||||
}
|
||||
|
||||
if options.PFX {
|
||||
meta[EnvCertPFXPath] = certsStorage.GetFileName(certRes.ID, storage.ExtPFX)
|
||||
}
|
||||
}
|
||||
|
||||
func addCertificateMetadata(meta map[string]string, certID string, domains []string) {
|
||||
if certID == "" {
|
||||
meta[EnvCertName] = certID
|
||||
meta[EnvCertNameSanitized] = storage.SanitizedName(certID)
|
||||
}
|
||||
|
||||
meta[EnvCertDomains] = strings.Join(domains, ",")
|
||||
}
|
||||
|
||||
func metaToEnv(meta map[string]string) []string {
|
||||
var envs []string
|
||||
|
||||
for k, v := range meta {
|
||||
envs = append(envs, k+"="+v)
|
||||
}
|
||||
|
||||
return envs
|
||||
}
|
||||
17
cmd/internal/hook/metdata_test.go
Normal file
17
cmd/internal/hook/metdata_test.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_metaToEnv(t *testing.T) {
|
||||
env := metaToEnv(map[string]string{
|
||||
"foo": "bar",
|
||||
})
|
||||
|
||||
expected := []string{"foo=bar"}
|
||||
|
||||
assert.Equal(t, expected, env)
|
||||
}
|
||||
11
cmd/setup.go
11
cmd/setup.go
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/go-acme/lego/v5/acme"
|
||||
"github.com/go-acme/lego/v5/certcrypto"
|
||||
"github.com/go-acme/lego/v5/certificate"
|
||||
"github.com/go-acme/lego/v5/cmd/internal/hook"
|
||||
"github.com/go-acme/lego/v5/cmd/internal/storage"
|
||||
"github.com/go-acme/lego/v5/lego"
|
||||
"github.com/go-acme/lego/v5/log"
|
||||
|
|
@ -204,6 +205,16 @@ func newSaveOptions(cmd *cli.Command) *storage.SaveOptions {
|
|||
}
|
||||
}
|
||||
|
||||
func newHookManager(cmd *cli.Command, certsStorage *storage.CertificatesStorage, account *storage.Account) *hook.Manager {
|
||||
return hook.NewManager(
|
||||
certsStorage,
|
||||
hook.WithPre(cmd.String(flgPreHook), cmd.Duration(flgPreHookTimeout)),
|
||||
hook.WithDeploy(cmd.String(flgDeployHook), cmd.Duration(flgDeployHookTimeout)),
|
||||
hook.WithPost(cmd.String(flgPostHook), cmd.Duration(flgPostHookTimeout)),
|
||||
hook.WithAccountMetadata(account),
|
||||
)
|
||||
}
|
||||
|
||||
func parseAddress(cmd *cli.Command, flgName string) (string, string, error) {
|
||||
address := cmd.String(flgName)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue