Explorar el Código

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>
Nicola Murino hace 2 años
padre
commit
80244bd83b

+ 2 - 1
go.mod

@@ -77,7 +77,8 @@ require (
 
 
 require (
 require (
 	cloud.google.com/go v0.105.0 // indirect
 	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
 	cloud.google.com/go/iam v0.6.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
 	github.com/ajg/form v1.5.1 // indirect
 	github.com/ajg/form v1.5.1 // indirect

+ 4 - 2
go.sum

@@ -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.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.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 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.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/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=

+ 27 - 1
internal/common/eventmanager.go

@@ -29,6 +29,7 @@ import (
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"path"
 	"path"
+	"path/filepath"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
@@ -476,6 +477,15 @@ func (p *EventParams) AddError(err error) {
 	p.errors = append(p.errors, 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 {
 func (p *EventParams) getStatusString() string {
 	switch p.Status {
 	switch p.Status {
 	case 1:
 	case 1:
@@ -503,6 +513,18 @@ func (p *EventParams) getUsers() ([]dataprovider.User, error) {
 }
 }
 
 
 func (p *EventParams) getUserFromSender() (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)
 	user, err := dataprovider.UserExists(p.sender)
 	if err != nil {
 	if err != nil {
 		eventManagerLog(logger.LevelError, "unable to get user %q: %+v", p.sender, err)
 		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:
 	case dataprovider.ActionTypeEmail:
 		err = executeEmailRuleAction(action.Options.EmailConfig, params)
 		err = executeEmailRuleAction(action.Options.EmailConfig, params)
 	case dataprovider.ActionTypeBackup:
 	case dataprovider.ActionTypeBackup:
-		err = dataprovider.ExecuteBackup()
+		var backupPath string
+		backupPath, err = dataprovider.ExecuteBackup()
+		if err == nil {
+			params.setBackupParams(backupPath)
+		}
 	case dataprovider.ActionTypeUserQuotaReset:
 	case dataprovider.ActionTypeUserQuotaReset:
 		err = executeUsersQuotaResetRuleAction(conditions, params)
 		err = executeUsersQuotaResetRuleAction(conditions, params)
 	case dataprovider.ActionTypeFolderQuotaReset:
 	case dataprovider.ActionTypeFolderQuotaReset:

+ 103 - 5
internal/common/protocol_test.go

@@ -3353,6 +3353,10 @@ func TestEventRule(t *testing.T) {
 	}
 	}
 	a2 := dataprovider.BaseEventAction{
 	a2 := dataprovider.BaseEventAction{
 		Name: "action2",
 		Name: "action2",
+		Type: dataprovider.ActionTypeBackup,
+	}
+	a3 := dataprovider.BaseEventAction{
+		Name: "action3",
 		Type: dataprovider.ActionTypeEmail,
 		Type: dataprovider.ActionTypeEmail,
 		Options: dataprovider.BaseEventActionOptions{
 		Options: dataprovider.BaseEventActionOptions{
 			EmailConfig: dataprovider.EventActionEmailConfig{
 			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,
 		Type: dataprovider.ActionTypeEmail,
 		Options: dataprovider.BaseEventActionOptions{
 		Options: dataprovider.BaseEventActionOptions{
 			EmailConfig: dataprovider.EventActionEmailConfig{
 			EmailConfig: dataprovider.EventActionEmailConfig{
@@ -3379,6 +3383,9 @@ func TestEventRule(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
 	action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
+	action4, _, err := httpdtest.AddEventAction(a4, http.StatusCreated)
+	assert.NoError(t, err)
+
 	r1 := dataprovider.EventRule{
 	r1 := dataprovider.EventRule{
 		Name:    "test rule1",
 		Name:    "test rule1",
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Trigger: dataprovider.EventTriggerFsEvent,
@@ -3417,6 +3424,12 @@ func TestEventRule(t *testing.T) {
 					Name: action3.Name,
 					Name: action3.Name,
 				},
 				},
 				Order: 3,
 				Order: 3,
+			},
+			{
+				BaseEventAction: dataprovider.BaseEventAction{
+					Name: action4.Name,
+				},
+				Order: 4,
 				Options: dataprovider.EventActionOptions{
 				Options: dataprovider.EventActionOptions{
 					IsFailureAction: true,
 					IsFailureAction: true,
 				},
 				},
@@ -3442,13 +3455,13 @@ func TestEventRule(t *testing.T) {
 		Actions: []dataprovider.EventAction{
 		Actions: []dataprovider.EventAction{
 			{
 			{
 				BaseEventAction: dataprovider.BaseEventAction{
 				BaseEventAction: dataprovider.BaseEventAction{
-					Name: action2.Name,
+					Name: action3.Name,
 				},
 				},
 				Order: 1,
 				Order: 1,
 			},
 			},
 			{
 			{
 				BaseEventAction: dataprovider.BaseEventAction{
 				BaseEventAction: dataprovider.BaseEventAction{
-					Name: action3.Name,
+					Name: action4.Name,
 				},
 				},
 				Order: 2,
 				Order: 2,
 				Options: dataprovider.EventActionOptions{
 				Options: dataprovider.EventActionOptions{
@@ -3469,7 +3482,7 @@ func TestEventRule(t *testing.T) {
 		Actions: []dataprovider.EventAction{
 		Actions: []dataprovider.EventAction{
 			{
 			{
 				BaseEventAction: dataprovider.BaseEventAction{
 				BaseEventAction: dataprovider.BaseEventAction{
-					Name: action2.Name,
+					Name: action3.Name,
 				},
 				},
 				Order: 1,
 				Order: 1,
 			},
 			},
@@ -3645,6 +3658,8 @@ func TestEventRule(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
 	_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
+	_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
+	assert.NoError(t, err)
 	lastReceivedEmail.reset()
 	lastReceivedEmail.reset()
 	_, err = httpdtest.RemoveUser(user, http.StatusOK)
 	_, err = httpdtest.RemoveUser(user, http.StatusOK)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -4237,6 +4252,89 @@ func TestEventFsActionsGroupFilters(t *testing.T) {
 	require.NoError(t, err)
 	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) {
 func TestEventActionHTTPMultipart(t *testing.T) {
 	a1 := dataprovider.BaseEventAction{
 	a1 := dataprovider.BaseEventAction{
 		Name: "action1",
 		Name: "action1",

+ 13 - 7
internal/dataprovider/dataprovider.go

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

+ 6 - 0
internal/dataprovider/eventrule.go

@@ -1354,6 +1354,12 @@ func (r *EventRule) hasUserAssociated(providerObjectType string) bool {
 		return providerObjectType == actionObjectUser
 		return providerObjectType == actionObjectUser
 	case EventTriggerFsEvent:
 	case EventTriggerFsEvent:
 		return true
 		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
 	return false
 }
 }

+ 16 - 0
internal/util/util.go

@@ -727,3 +727,19 @@ func PanicOnError(err error) {
 		panic(fmt.Errorf("unexpected error: %w", err))
 		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
+}