eventmanager: allow to access the backup file

so it can be used in email and other actions

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-10-29 14:04:31 +02:00
parent 9a9e7d1a7f
commit 80244bd83b
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
7 changed files with 171 additions and 16 deletions

3
go.mod
View file

@ -77,7 +77,8 @@ require (
require (
cloud.google.com/go v0.105.0 // indirect
cloud.google.com/go/compute/metadata v0.2.0 // indirect
cloud.google.com/go/compute v1.12.1 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect
cloud.google.com/go/iam v0.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
github.com/ajg/form v1.5.1 // indirect

6
go.sum
View file

@ -50,8 +50,10 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute/metadata v0.2.0 h1:nBbNSZyDpkNlo3DepaaLKVuO7ClyifSAmNloSCZrHnQ=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=

View file

@ -29,6 +29,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
@ -476,6 +477,15 @@ func (p *EventParams) AddError(err error) {
p.errors = append(p.errors, err.Error())
}
func (p *EventParams) setBackupParams(backupPath string) {
if p.sender != "" {
return
}
p.sender = dataprovider.ActionExecutorSystem
p.FsPath = backupPath
p.VirtualPath = filepath.Base(backupPath)
}
func (p *EventParams) getStatusString() string {
switch p.Status {
case 1:
@ -503,6 +513,18 @@ func (p *EventParams) getUsers() ([]dataprovider.User, error) {
}
func (p *EventParams) getUserFromSender() (dataprovider.User, error) {
if p.sender == dataprovider.ActionExecutorSystem {
return dataprovider.User{
BaseUser: sdk.BaseUser{
Status: 1,
Username: p.sender,
HomeDir: dataprovider.GetBackupsPath(),
Permissions: map[string][]string{
"/": {dataprovider.PermAny},
},
},
}, nil
}
user, err := dataprovider.UserExists(p.sender)
if err != nil {
eventManagerLog(logger.LevelError, "unable to get user %q: %+v", p.sender, err)
@ -1903,7 +1925,11 @@ func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams,
case dataprovider.ActionTypeEmail:
err = executeEmailRuleAction(action.Options.EmailConfig, params)
case dataprovider.ActionTypeBackup:
err = dataprovider.ExecuteBackup()
var backupPath string
backupPath, err = dataprovider.ExecuteBackup()
if err == nil {
params.setBackupParams(backupPath)
}
case dataprovider.ActionTypeUserQuotaReset:
err = executeUsersQuotaResetRuleAction(conditions, params)
case dataprovider.ActionTypeFolderQuotaReset:

View file

@ -3353,6 +3353,10 @@ func TestEventRule(t *testing.T) {
}
a2 := dataprovider.BaseEventAction{
Name: "action2",
Type: dataprovider.ActionTypeBackup,
}
a3 := dataprovider.BaseEventAction{
Name: "action3",
Type: dataprovider.ActionTypeEmail,
Options: dataprovider.BaseEventActionOptions{
EmailConfig: dataprovider.EventActionEmailConfig{
@ -3362,8 +3366,8 @@ func TestEventRule(t *testing.T) {
},
},
}
a3 := dataprovider.BaseEventAction{
Name: "action3",
a4 := dataprovider.BaseEventAction{
Name: "action4",
Type: dataprovider.ActionTypeEmail,
Options: dataprovider.BaseEventActionOptions{
EmailConfig: dataprovider.EventActionEmailConfig{
@ -3379,6 +3383,9 @@ func TestEventRule(t *testing.T) {
assert.NoError(t, err)
action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
assert.NoError(t, err)
action4, _, err := httpdtest.AddEventAction(a4, http.StatusCreated)
assert.NoError(t, err)
r1 := dataprovider.EventRule{
Name: "test rule1",
Trigger: dataprovider.EventTriggerFsEvent,
@ -3417,6 +3424,12 @@ func TestEventRule(t *testing.T) {
Name: action3.Name,
},
Order: 3,
},
{
BaseEventAction: dataprovider.BaseEventAction{
Name: action4.Name,
},
Order: 4,
Options: dataprovider.EventActionOptions{
IsFailureAction: true,
},
@ -3442,13 +3455,13 @@ func TestEventRule(t *testing.T) {
Actions: []dataprovider.EventAction{
{
BaseEventAction: dataprovider.BaseEventAction{
Name: action2.Name,
Name: action3.Name,
},
Order: 1,
},
{
BaseEventAction: dataprovider.BaseEventAction{
Name: action3.Name,
Name: action4.Name,
},
Order: 2,
Options: dataprovider.EventActionOptions{
@ -3469,7 +3482,7 @@ func TestEventRule(t *testing.T) {
Actions: []dataprovider.EventAction{
{
BaseEventAction: dataprovider.BaseEventAction{
Name: action2.Name,
Name: action3.Name,
},
Order: 1,
},
@ -3645,6 +3658,8 @@ func TestEventRule(t *testing.T) {
assert.NoError(t, err)
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
assert.NoError(t, err)
lastReceivedEmail.reset()
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
@ -4237,6 +4252,89 @@ func TestEventFsActionsGroupFilters(t *testing.T) {
require.NoError(t, err)
}
func TestBackupAsAttachment(t *testing.T) {
smtpCfg := smtp.Config{
Host: "127.0.0.1",
Port: 2525,
From: "notification@example.com",
TemplatesPath: "templates",
}
err := smtpCfg.Initialize(configDir)
require.NoError(t, err)
a1 := dataprovider.BaseEventAction{
Name: "a1",
Type: dataprovider.ActionTypeBackup,
}
a2 := dataprovider.BaseEventAction{
Name: "a2",
Type: dataprovider.ActionTypeEmail,
Options: dataprovider.BaseEventActionOptions{
EmailConfig: dataprovider.EventActionEmailConfig{
Recipients: []string{"test@example.com"},
Subject: `"{{Event}} {{StatusString}}"`,
Body: "Domain: {{Name}}",
Attachments: []string{"/{{VirtualPath}}"},
},
},
}
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
assert.NoError(t, err)
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
assert.NoError(t, err)
r1 := dataprovider.EventRule{
Name: "test rule certificate",
Trigger: dataprovider.EventTriggerCertificate,
Actions: []dataprovider.EventAction{
{
BaseEventAction: dataprovider.BaseEventAction{
Name: action1.Name,
},
Order: 1,
},
{
BaseEventAction: dataprovider.BaseEventAction{
Name: action2.Name,
},
Order: 2,
},
},
}
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
assert.NoError(t, err)
lastReceivedEmail.reset()
renewalEvent := "Certificate renewal"
common.HandleCertificateEvent(common.EventParams{
Name: "example.com",
Timestamp: time.Now().UnixNano(),
Status: 1,
Event: renewalEvent,
})
assert.Eventually(t, func() bool {
return lastReceivedEmail.get().From != ""
}, 3000*time.Millisecond, 100*time.Millisecond)
email := lastReceivedEmail.get()
assert.Len(t, email.To, 1)
assert.True(t, util.Contains(email.To, "test@example.com"))
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
assert.Contains(t, email.Data, `Domain: example.com`)
assert.Contains(t, email.Data, "Content-Type: application/json")
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
assert.NoError(t, err)
smtpCfg = smtp.Config{}
err = smtpCfg.Initialize(configDir)
require.NoError(t, err)
}
func TestEventActionHTTPMultipart(t *testing.T) {
a1 := dataprovider.BaseEventAction{
Name: "action1",

View file

@ -524,36 +524,36 @@ func (c *Config) requireCustomTLSForMySQL() bool {
return false
}
func (c *Config) doBackup() error {
func (c *Config) doBackup() (string, error) {
now := time.Now().UTC()
outputFile := filepath.Join(c.BackupsPath, fmt.Sprintf("backup_%s_%d.json", now.Weekday(), now.Hour()))
providerLog(logger.LevelDebug, "starting backup to file %q", outputFile)
err := os.MkdirAll(filepath.Dir(outputFile), 0700)
if err != nil {
providerLog(logger.LevelError, "unable to create backup dir %q: %v", outputFile, err)
return fmt.Errorf("unable to create backup dir: %w", err)
return outputFile, fmt.Errorf("unable to create backup dir: %w", err)
}
backup, err := DumpData()
if err != nil {
providerLog(logger.LevelError, "unable to execute backup: %v", err)
return fmt.Errorf("unable to dump backup data: %w", err)
return outputFile, fmt.Errorf("unable to dump backup data: %w", err)
}
dump, err := json.Marshal(backup)
if err != nil {
providerLog(logger.LevelError, "unable to marshal backup as JSON: %v", err)
return fmt.Errorf("unable to marshal backup data as JSON: %w", err)
return outputFile, fmt.Errorf("unable to marshal backup data as JSON: %w", err)
}
err = os.WriteFile(outputFile, dump, 0600)
if err != nil {
providerLog(logger.LevelError, "unable to save backup: %v", err)
return fmt.Errorf("unable to save backup: %w", err)
return outputFile, fmt.Errorf("unable to save backup: %w", err)
}
providerLog(logger.LevelDebug, "backup saved to %q", outputFile)
return nil
return outputFile, nil
}
// ExecuteBackup executes a backup
func ExecuteBackup() error {
func ExecuteBackup() (string, error) {
return config.doBackup()
}
@ -833,6 +833,12 @@ func Initialize(cnf Config, basePath string, checkAdmins bool) error {
if cnf.BackupsPath == "" {
return fmt.Errorf("required directory is invalid, backup path %#v", cnf.BackupsPath)
}
absoluteBackupPath, err := util.GetAbsolutePath(cnf.BackupsPath)
if err != nil {
return fmt.Errorf("unable to get absolute backup path: %w", err)
}
config.BackupsPath = absoluteBackupPath
providerLog(logger.LevelDebug, "absolute backup path %q", config.BackupsPath)
if err := initializeHashingAlgo(&cnf); err != nil {
return err

View file

@ -1354,6 +1354,12 @@ func (r *EventRule) hasUserAssociated(providerObjectType string) bool {
return providerObjectType == actionObjectUser
case EventTriggerFsEvent:
return true
default:
if len(r.Actions) > 0 {
// should we allow schedules where backup is not the first action?
// maybe we could pass the action index and check before that index
return r.Actions[0].Type == ActionTypeBackup
}
}
return false
}

View file

@ -727,3 +727,19 @@ func PanicOnError(err error) {
panic(fmt.Errorf("unexpected error: %w", err))
}
}
// GetAbsolutePath returns an absolute path using the current dir as base
// if name defines a relative path
func GetAbsolutePath(name string) (string, error) {
if name == "" {
return name, errors.New("input path cannot be empty")
}
if filepath.IsAbs(name) {
return name, nil
}
curDir, err := os.Getwd()
if err != nil {
return name, err
}
return filepath.Join(curDir, name), nil
}