Skip to content

Commit

Permalink
Optionally delete commands after results are received in mysql storag…
Browse files Browse the repository at this point in the history
…e backend
  • Loading branch information
jessepeterson committed May 25, 2022
1 parent 2fb9eee commit 37e877e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 8 deletions.
34 changes: 30 additions & 4 deletions cmd/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,38 @@ func fileStorageConfig(dsn, options string) (*file.FileStorage, error) {
}

func mysqlStorageConfig(dsn, options string, logger log.Logger) (*mysql.MySQLStorage, error) {
if options != "" {
return nil, NoStorageOptions
}
logger = logger.With("storage", "mysql")
opts := []mysql.Option{
mysql.WithDSN(dsn),
mysql.WithLogger(logger.With("storage", "mysql")),
mysql.WithLogger(logger),
}
if options != "" {
for k, v := range splitOptions(options) {
switch k {
case "delete":
if v == "1" {
opts = append(opts, mysql.WithDeleteCommands())
logger.Debug("msg", "deleting commands")
} else if v != "0" {
return nil, fmt.Errorf("invalid value for delete option: %q", v)
}
default:
return nil, fmt.Errorf("invalid option: %q", k)
}
}
}
return mysql.New(opts...)
}

func splitOptions(s string) map[string]string {
out := make(map[string]string)
opts := strings.Split(s, ",")
for _, opt := range opts {
optKAndV := strings.SplitN(opt, "=", 2)
if len(optKAndV) < 2 {
optKAndV = append(optKAndV, "")
}
out[optKAndV[0]] = optKAndV[1]
}
return out
}
9 changes: 8 additions & 1 deletion docs/operations-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The `-storage`, `-dsn`, & `-storage-options` flags together configure the storag

* `-storage file`

Configures the `file` storage backend. This manages enrollment and command data within plain filesystem files and directories. It has zero dependencies and should run out of the box. The `-dsn` flag specifies the filesystem directory for the database.
Configures the `file` storage backend. This manages enrollment and command data within plain filesystem files and directories. It has zero dependencies and should run out of the box. The `-dsn` flag specifies the filesystem directory for the database. The `file` backend has no storage options.

*Example:* `-storage file -dsn /path/to/my/db`

Expand All @@ -56,6 +56,13 @@ Configures the MySQL storage backend. The `-dsn` flag should be in the [format t

*Example:* `-storage mysql -dsn nanomdm:nanomdm/mymdmdb`

Options are specified as a comma-separated list of "key=value" pairs. The mysql backend supports these options:

* `delete=1`, `delete=0`
* This option turns on the command and response deleter. When enabled (with `delete=1`) command responses, queued commands, and commands themeselves will be deleted from the database after enrollments have responded to a command.

*Example:* `-storage mysql -dsn nanomdm:nanomdm/mymdmdb -storage-options delete=1`

#### multi-storage backend

You can configure multiple storage backends to be used simultaneously. Specifying multiple sets of `-storage`, `-dsn`, & `-storage-options` flags will configure the "multi-storage" adapter. The flags must be specified in sets and are related to each other in the order they're specified: for example the first `-storage` flag corresponds to the first `-dsn` flag and so forth.
Expand Down
10 changes: 9 additions & 1 deletion storage/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ var ErrNoCert = errors.New("no certificate in MDM Request")
type MySQLStorage struct {
logger log.Logger
db *sql.DB
rm bool
}

type config struct {
driver string
dsn string
db *sql.DB
logger log.Logger
rm bool
}

type Option func(*config)
Expand Down Expand Up @@ -53,6 +55,12 @@ func WithDB(db *sql.DB) Option {
}
}

func WithDeleteCommands() Option {
return func(c *config) {
c.rm = true
}
}

func New(opts ...Option) (*MySQLStorage, error) {
cfg := &config{logger: log.NopLogger, driver: "mysql"}
for _, opt := range opts {
Expand All @@ -68,7 +76,7 @@ func New(opts ...Option) (*MySQLStorage, error) {
if err = cfg.db.Ping(); err != nil {
return nil, err
}
return &MySQLStorage{db: cfg.db, logger: cfg.logger}, nil
return &MySQLStorage{db: cfg.db, logger: cfg.logger, rm: cfg.rm}, nil
}

// nullEmptyString returns a NULL string if s is empty.
Expand Down
53 changes: 53 additions & 0 deletions storage/mysql/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,66 @@ func (m *MySQLStorage) EnqueueCommand(ctx context.Context, ids []string, cmd *md
return nil, tx.Commit()
}

func (s *MySQLStorage) deleteCommand(ctx context.Context, tx *sql.Tx, id, uuid string) error {
// delete command result (i.e. NotNows) and this queued command
_, err := tx.ExecContext(
ctx, `
DELETE
q, r
FROM
enrollment_queue AS q
LEFT JOIN command_results AS r
ON q.command_uuid = r.command_uuid AND r.id = q.id
WHERE
q.id = ? AND q.command_uuid = ?;
`,
id, uuid,
)
if err != nil {
return err
}
// now delete the actual command assuming any remaining queued
// enrolmments have been deleted
_, err = tx.ExecContext(
ctx, `
DELETE
c
FROM
commands AS c
LEFT JOIN enrollment_queue AS q
ON q.command_uuid = c.command_uuid
WHERE
c.command_uuid = ? AND q.id IS NULL;
`,
uuid,
)
return err
}

func (s *MySQLStorage) deleteCommandTx(r *mdm.Request, result *mdm.CommandResults) error {
tx, err := s.db.BeginTx(r.Context, nil)
if err != nil {
return err
}
if err = s.deleteCommand(r.Context, tx, r.ID, result.CommandUUID); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("rollback error: %w; while trying to handle error: %v", rbErr, err)
}
return err
}
return tx.Commit()
}

func (s *MySQLStorage) StoreCommandReport(r *mdm.Request, result *mdm.CommandResults) error {
if err := s.updateLastSeen(r); err != nil {
return err
}
if result.Status == "Idle" {
return nil
}
if s.rm && (result.Status == "Acknowledged" || result.Status == "Error") {
return s.deleteCommandTx(r, result)
}
notNowConstants := "NULL, 0"
notNowBumpTallySQL := ""
// note that due to the ON DUPLICATE KEY we don't UPDATE the
Expand Down
15 changes: 13 additions & 2 deletions storage/mysql/queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestQueue(t *testing.T) {
t.Fatal("MySQL DSN flag not provided to test")
}

storage, err := New(WithDSN(*flDSN))
storage, err := New(WithDSN(*flDSN), WithDeleteCommands())
if err != nil {
t.Fatal(err)
}
Expand All @@ -97,5 +97,16 @@ func TestQueue(t *testing.T) {
t.Fatal(err)
}

test.TestQueue(t, deviceUDID, storage)
t.Run("WithDeleteCommands()", func(t *testing.T) {
test.TestQueue(t, deviceUDID, storage)
})

storage, err = New(WithDSN(*flDSN))
if err != nil {
t.Fatal(err)
}

t.Run("normal", func(t *testing.T) {
test.TestQueue(t, deviceUDID, storage)
})
}

0 comments on commit 37e877e

Please sign in to comment.