mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-10 11:36:06 +08:00
add delete method for profiles
This commit is contained in:
@@ -2,6 +2,7 @@ package workflow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
@@ -15,15 +16,21 @@ type Datastore interface {
|
||||
// Create adds a new workflow to the datastore
|
||||
CreateWorkflow(wf *Workflow) (*Workflow, error)
|
||||
|
||||
// Workflows can query the datastore for workflows
|
||||
// Workflows accepts one or more params as filters
|
||||
// to narrow down the number of results
|
||||
Workflows(params ...interface{}) ([]Workflow, error)
|
||||
|
||||
// CreateProfile adds a new profile to the datastore,
|
||||
// If a profile already exists, an error will be returned
|
||||
CreateProfile(pr *Profile) (*Profile, error)
|
||||
CreateProfile(p *Profile) (*Profile, error)
|
||||
|
||||
// Profiles can query the datastore for one or more profiles
|
||||
// and accepts one or more params as filters
|
||||
// Example: Profiles(Identifier{"com.example.id")}
|
||||
// DeleteProfile removes a profile from the datastore
|
||||
DeleteProfile(pr *Profile) error
|
||||
|
||||
// Profiles can query the datastore for profiles
|
||||
// Profiles accepts one or more params as filters
|
||||
// to narrow down the number of results
|
||||
Profiles(params ...interface{}) ([]Profile, error)
|
||||
}
|
||||
|
||||
@@ -36,6 +43,22 @@ type whereer interface {
|
||||
where() string
|
||||
}
|
||||
|
||||
// add WHERE clause from params
|
||||
func addWhereFilters(stmt string, params ...interface{}) string {
|
||||
var where []string
|
||||
for _, param := range params {
|
||||
if f, ok := param.(whereer); ok {
|
||||
where = append(where, f.where())
|
||||
}
|
||||
}
|
||||
|
||||
if len(where) != 0 {
|
||||
whereFilter := strings.Join(where, ",")
|
||||
stmt = fmt.Sprintf("%s WHERE %s", selectProfilesStmt, whereFilter)
|
||||
}
|
||||
return stmt
|
||||
}
|
||||
|
||||
//NewDB creates a Datastore
|
||||
func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) {
|
||||
switch driver {
|
||||
@@ -76,7 +99,7 @@ func migrate(db *sqlx.DB) {
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
profile_uuid uuid PRIMARY KEY
|
||||
DEFAULT uuid_generate_v4(),
|
||||
identifier text UNIQUE NOT NULL,
|
||||
payload_identifier text UNIQUE NOT NULL CHECK (payload_identifier <> ''),
|
||||
profile_data bytea
|
||||
);
|
||||
|
||||
|
||||
56
workflow/datastore_test.go
Normal file
56
workflow/datastore_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Test that a new datastore is succesfuly created and destroyed.
|
||||
func TestNewDB(t *testing.T) {
|
||||
_ = datastore(t)
|
||||
defer teardown()
|
||||
}
|
||||
|
||||
var (
|
||||
testConn = "user=micromdm password=micromdm dbname=micromdm sslmode=disable"
|
||||
)
|
||||
|
||||
func datastore(t *testing.T) Datastore {
|
||||
logger := log.NewLogfmtLogger(os.Stderr)
|
||||
ds, err := NewDB("postgres", testConn, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return ds
|
||||
}
|
||||
|
||||
func teardown() {
|
||||
db, err := sqlx.Open("postgres", testConn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
drop := `
|
||||
DROP TABLE IF EXISTS workflow_profile;
|
||||
DROP TABLE IF EXISTS workflow_workflow;
|
||||
DROP TABLE IF EXISTS profiles;
|
||||
DROP TABLE IF EXISTS workflows;
|
||||
`
|
||||
db.MustExec(drop)
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
func randomString(strlen int) string {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
result := make([]byte, strlen)
|
||||
for i := 0; i < strlen; i++ {
|
||||
result[i] = chars[rand.Intn(len(chars))]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
@@ -3,6 +3,6 @@ package workflow
|
||||
// Profile is an Apple Configuration profile
|
||||
type Profile struct {
|
||||
UUID string `plist:"-" json:"profile_uuid,omitempty" db:"profile_uuid"`
|
||||
PayloadIdentifier string `json:"payload_identifier" db:"identifier"`
|
||||
PayloadIdentifier string `json:"payload_identifier" db:"payload_identifier"`
|
||||
ProfileData string `json:"profile_data,omitempty" db:"profile_data"`
|
||||
}
|
||||
|
||||
@@ -3,19 +3,39 @@ package workflow
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// sql statements
|
||||
var (
|
||||
addProfileStmt = `INSERT INTO profiles (identifier, profile_data) VALUES ($1, $2)
|
||||
ON CONFLICT ON CONSTRAINT profiles_identifier_key DO NOTHING
|
||||
RETURNING profile_uuid;`
|
||||
selectProfilesStmt = `SELECT profile_uuid, identifier FROM profiles`
|
||||
addProfileStmt = `INSERT INTO profiles
|
||||
(payload_identifier, profile_data) VALUES ($1, $2)
|
||||
ON CONFLICT ON CONSTRAINT profiles_payload_identifier_key DO NOTHING
|
||||
RETURNING profile_uuid;`
|
||||
|
||||
selectProfilesStmt = `SELECT profile_uuid, payload_identifier, profile_data FROM profiles`
|
||||
deleteProfileStmt = `DELETE FROM profiles`
|
||||
)
|
||||
|
||||
// ProfileUUID is a filter we can add as a parameter to narrow down the list of returned results
|
||||
type ProfileUUID struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
func (p ProfileUUID) where() string {
|
||||
return fmt.Sprintf("profile_uuid = '%s'", p.UUID)
|
||||
}
|
||||
|
||||
// PayloadIdentifier is a filter we can add as a parameter to narrow down the list of returned results
|
||||
type PayloadIdentifier struct {
|
||||
PayloadIdentifier string
|
||||
}
|
||||
|
||||
func (p PayloadIdentifier) where() string {
|
||||
return fmt.Sprintf("payload_identifier = '%s'", p.PayloadIdentifier)
|
||||
}
|
||||
|
||||
func (store pgStore) CreateProfile(prf *Profile) (*Profile, error) {
|
||||
err := store.QueryRow(addProfileStmt, prf.PayloadIdentifier, prf.ProfileData).Scan(&prf.UUID)
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -27,19 +47,24 @@ func (store pgStore) CreateProfile(prf *Profile) (*Profile, error) {
|
||||
return prf, nil
|
||||
}
|
||||
|
||||
func (store pgStore) DeleteProfile(p *Profile) error {
|
||||
stmt := deleteProfileStmt
|
||||
if p.UUID == "" || p.PayloadIdentifier == "" {
|
||||
return nil // just don't do anything
|
||||
}
|
||||
if p.UUID != "" {
|
||||
stmt = addWhereFilters(stmt, ProfileUUID{p.UUID})
|
||||
}
|
||||
_, err := store.Exec(stmt)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "delete profile")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store pgStore) Profiles(params ...interface{}) ([]Profile, error) {
|
||||
stmt := selectProfilesStmt
|
||||
var where []string
|
||||
for _, param := range params {
|
||||
if f, ok := param.(whereer); ok {
|
||||
where = append(where, f.where())
|
||||
}
|
||||
}
|
||||
|
||||
if len(where) != 0 {
|
||||
whereFilter := strings.Join(where, ",")
|
||||
stmt = fmt.Sprintf("%s WHERE %s", selectProfilesStmt, whereFilter)
|
||||
}
|
||||
stmt = addWhereFilters(stmt, params...)
|
||||
|
||||
var profiles []Profile
|
||||
err := store.Select(&profiles, stmt)
|
||||
|
||||
@@ -1 +1,159 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
func TestDeleteProfile(t *testing.T) {
|
||||
ds := datastore(t)
|
||||
defer teardown()
|
||||
testProfiles := addTestProfiles(t, ds, 5)
|
||||
|
||||
for _, p := range testProfiles {
|
||||
err := ds.DeleteProfile(&p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
empty := Profile{}
|
||||
err := ds.DeleteProfile(&empty)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
badUUIDProfile := Profile{
|
||||
UUID: "bad.uuid",
|
||||
PayloadIdentifier: "with.bad.uuid",
|
||||
}
|
||||
err = ds.DeleteProfile(&badUUIDProfile)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error but got nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRetrieveProfiles(t *testing.T) {
|
||||
ds := datastore(t)
|
||||
defer teardown()
|
||||
testProfiles := addTestProfiles(t, ds, 5)
|
||||
// retrieve all
|
||||
profiles, err := ds.Profiles()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(profiles) != 5 {
|
||||
t.Error("expected", 5, "got", len(profiles))
|
||||
}
|
||||
|
||||
for _, p := range testProfiles {
|
||||
byUUID, err := ds.Profiles(ProfileUUID{p.UUID})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(byUUID) != 1 {
|
||||
t.Log("filtering by UUID should only return 1 result")
|
||||
t.Fatal("expected", 1, "got", len(byUUID))
|
||||
}
|
||||
|
||||
uuid := byUUID[0].UUID
|
||||
if p.UUID != uuid {
|
||||
t.Log("result should have the same UUID as the one in the query")
|
||||
t.Fatal("expected", p.UUID, "got", uuid)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
badUUIDQuery := ProfileUUID{"bad_uuid"}
|
||||
_, err = ds.Profiles(badUUIDQuery)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error but got nil")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Generates new Profile types and stores them in the datastore
|
||||
func TestCreateProfile(t *testing.T) {
|
||||
ds := datastore(t)
|
||||
defer teardown()
|
||||
|
||||
assertion := func(pf Profile) bool {
|
||||
newPrf, err := ds.CreateProfile(&pf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return false
|
||||
}
|
||||
if newPrf.UUID == "" || newPrf.PayloadIdentifier != pf.PayloadIdentifier {
|
||||
return false
|
||||
}
|
||||
|
||||
// now try duplicates
|
||||
|
||||
_, err = ds.CreateProfile(&pf)
|
||||
if err != ErrExists || err == nil {
|
||||
t.Log("ds should not create duplicate resources")
|
||||
t.Fatal("expected", ErrExists, "got", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if err := quick.Check(assertion, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// empty profile test
|
||||
empty := Profile{}
|
||||
_, err := ds.CreateProfile(&empty)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// add some profiles to the datastore for quick testing
|
||||
func addTestProfiles(t *testing.T, ds Datastore, numProfiles int) []Profile {
|
||||
var profiles []Profile
|
||||
for i := 0; i < numProfiles; i++ {
|
||||
input := randomProfile()
|
||||
newProfile, err := ds.CreateProfile(&input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
profiles = append(profiles, *newProfile)
|
||||
}
|
||||
return profiles
|
||||
}
|
||||
|
||||
func randomProfile() Profile {
|
||||
vrf, ok := quick.Value(reflect.TypeOf(Profile{}), rand.New(rand.NewSource(1)))
|
||||
if !ok {
|
||||
panic("randomProfile: no value")
|
||||
}
|
||||
if f, ok := vrf.Interface().(Profile); ok {
|
||||
return f
|
||||
}
|
||||
return Profile{}
|
||||
}
|
||||
|
||||
// Generate a random profile
|
||||
func (pf Profile) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
a := randomString(3)
|
||||
b := randomString(10)
|
||||
c := randomString(5)
|
||||
data, err := ioutil.ReadFile("testdata/sample_profile")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
randomIdnetifier := fmt.Sprintf("%v.%v.%v", a, b, c)
|
||||
randomProfile := Profile{
|
||||
PayloadIdentifier: randomIdnetifier,
|
||||
ProfileData: string(data),
|
||||
}
|
||||
return reflect.ValueOf(randomProfile)
|
||||
|
||||
}
|
||||
|
||||
61
workflow/testdata/sample_profile
vendored
Normal file
61
workflow/testdata/sample_profile
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBV
|
||||
QkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20v
|
||||
RFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4K
|
||||
CTxrZXk+UGF5bG9hZENvbnRlbnQ8L2tleT4KCTxhcnJheT4KCQk8ZGljdD4KCQkJPGtleT5BZG1p
|
||||
bkhvc3RJbmZvPC9rZXk+CgkJCTxzdHJpbmc+SG9zdE5hbWU8L3N0cmluZz4KCQkJPGtleT5BZG1p
|
||||
bk1heURpc2FibGVNQ1g8L2tleT4KCQkJPGZhbHNlLz4KCQkJPGtleT5BbGxvd0xpc3Q8L2tleT4K
|
||||
CQkJPGFycmF5Lz4KCQkJPGtleT5BbHdheXNTaG93V29ya2dyb3VwRGlhbG9nPC9rZXk+CgkJCTxm
|
||||
YWxzZS8+CgkJCTxrZXk+Q29tYmluZVVzZXJXb3JrZ3JvdXBzPC9rZXk+CgkJCTx0cnVlLz4KCQkJ
|
||||
PGtleT5EZW55TGlzdDwva2V5PgoJCQk8YXJyYXkvPgoJCQk8a2V5PkRpc2FibGVDb25zb2xlQWNj
|
||||
ZXNzPC9rZXk+CgkJCTxmYWxzZS8+CgkJCTxrZXk+RW5hYmxlRXh0ZXJuYWxBY2NvdW50czwva2V5
|
||||
PgoJCQk8ZmFsc2UvPgoJCQk8a2V5PkZsYXR0ZW5Vc2VyV29ya2dyb3Vwczwva2V5PgoJCQk8ZmFs
|
||||
c2UvPgoJCQk8a2V5PkhpZGVBZG1pblVzZXJzPC9rZXk+CgkJCTxmYWxzZS8+CgkJCTxrZXk+SGlk
|
||||
ZUxvY2FsVXNlcnM8L2tleT4KCQkJPGZhbHNlLz4KCQkJPGtleT5IaWRlTW9iaWxlQWNjb3VudHM8
|
||||
L2tleT4KCQkJPGZhbHNlLz4KCQkJPGtleT5JbmNsdWRlTmV0d29ya1VzZXI8L2tleT4KCQkJPGZh
|
||||
bHNlLz4KCQkJPGtleT5Mb2NhbFVzZXJMb2dpbkVuYWJsZWQ8L2tleT4KCQkJPHRydWUvPgoJCQk8
|
||||
a2V5PkxvY2FsVXNlcnNIYXZlV29ya2dyb3Vwczwva2V5PgoJCQk8ZmFsc2UvPgoJCQk8a2V5PlBh
|
||||
eWxvYWREaXNwbGF5TmFtZTwva2V5PgoJCQk8c3RyaW5nPkxvZ2luIFdpbmRvdzwvc3RyaW5nPgoJ
|
||||
CQk8a2V5PlBheWxvYWRFbmFibGVkPC9rZXk+CgkJCTx0cnVlLz4KCQkJPGtleT5QYXlsb2FkSWRl
|
||||
bnRpZmllcjwva2V5PgoJCQk8c3RyaW5nPmNvbS5naXRodWIuZ3JlZ25lYWdsZS5sb2dpbndpbmRv
|
||||
dy5sb2dpbndpbmRvdzwvc3RyaW5nPgoJCQk8a2V5PlBheWxvYWRUeXBlPC9rZXk+CgkJCTxzdHJp
|
||||
bmc+Y29tLmFwcGxlLmxvZ2lud2luZG93PC9zdHJpbmc+CgkJCTxrZXk+UGF5bG9hZFVVSUQ8L2tl
|
||||
eT4KCQkJPHN0cmluZz5mMjY0ZGQ4Mi01NzMwLTcxNDktMDI0Zi1iNDc1ZjVlOGMwOTA8L3N0cmlu
|
||||
Zz4KCQkJPGtleT5QYXlsb2FkVmVyc2lvbjwva2V5PgoJCQk8aW50ZWdlcj4xPC9pbnRlZ2VyPgoJ
|
||||
CQk8a2V5PlJlc3RhcnREaXNhYmxlZDwva2V5PgoJCQk8ZmFsc2UvPgoJCQk8a2V5PlJldHJpZXNV
|
||||
bnRpbEhpbnQ8L2tleT4KCQkJPGludGVnZXI+MzwvaW50ZWdlcj4KCQkJPGtleT5TSE9XRlVMTE5B
|
||||
TUU8L2tleT4KCQkJPHRydWUvPgoJCQk8a2V5PlNIT1dPVEhFUlVTRVJTX01BTkFHRUQ8L2tleT4K
|
||||
CQkJPGZhbHNlLz4KCQkJPGtleT5TaHV0RG93bkRpc2FibGVkPC9rZXk+CgkJCTxmYWxzZS8+CgkJ
|
||||
CTxrZXk+U2xlZXBEaXNhYmxlZDwva2V5PgoJCQk8ZmFsc2UvPgoJCQk8a2V5PlVzZUNvbXB1dGVy
|
||||
TmFtZUZvckNvbXB1dGVyUmVjb3JkTmFtZTwva2V5PgoJCQk8ZmFsc2UvPgoJCQk8a2V5PmNvbS5h
|
||||
cHBsZS5sb2dpbi5tY3guRGlzYWJsZUF1dG9Mb2dpbkNsaWVudDwva2V5PgoJCQk8dHJ1ZS8+CgkJ
|
||||
PC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5PkRpc2FibGVHdWVzdEFjY291bnQ8L2tleT4KCQkJPHRy
|
||||
dWUvPgoJCQk8a2V5PkVuYWJsZUd1ZXN0QWNjb3VudDwva2V5PgoJCQk8ZmFsc2UvPgoJCQk8a2V5
|
||||
PlBheWxvYWREaXNwbGF5TmFtZTwva2V5PgoJCQk8c3RyaW5nPkxvZ2luIFdpbmRvdzogIE1DWCBQ
|
||||
cmVmZXJlbmNlczwvc3RyaW5nPgoJCQk8a2V5PlBheWxvYWRFbmFibGVkPC9rZXk+CgkJCTx0cnVl
|
||||
Lz4KCQkJPGtleT5QYXlsb2FkSWRlbnRpZmllcjwva2V5PgoJCQk8c3RyaW5nPmNvbS5naXRodWIu
|
||||
Z3JlZ25lYWdsZS5sb2dpbndpbmRvdy5NQ1g8L3N0cmluZz4KCQkJPGtleT5QYXlsb2FkVHlwZTwv
|
||||
a2V5PgoJCQk8c3RyaW5nPmNvbS5hcHBsZS5NQ1g8L3N0cmluZz4KCQkJPGtleT5QYXlsb2FkVVVJ
|
||||
RDwva2V5PgoJCQk8c3RyaW5nPjQ5N2MzNmQxLTQyMmMtYjVlMC1jMjNlLWIxMTM4YjJkNDkwYTwv
|
||||
c3RyaW5nPgoJCQk8a2V5PlBheWxvYWRWZXJzaW9uPC9rZXk+CgkJCTxpbnRlZ2VyPjE8L2ludGVn
|
||||
ZXI+CgkJCTxrZXk+U2xlZXBEaXNhYmxlZDwva2V5PgoJCQk8ZmFsc2UvPgoJCTwvZGljdD4KCQk8
|
||||
ZGljdD4KCQkJPGtleT5NdWx0aXBsZVNlc3Npb25FbmFibGVkPC9rZXk+CgkJCTxmYWxzZS8+CgkJ
|
||||
CTxrZXk+UGF5bG9hZERpc3BsYXlOYW1lPC9rZXk+CgkJCTxzdHJpbmc+TG9naW4gV2luZG93OiAg
|
||||
R2xvYmFsIFByZWZlcmVuY2VzPC9zdHJpbmc+CgkJCTxrZXk+UGF5bG9hZEVuYWJsZWQ8L2tleT4K
|
||||
CQkJPHRydWUvPgoJCQk8a2V5PlBheWxvYWRJZGVudGlmaWVyPC9rZXk+CgkJCTxzdHJpbmc+Y29t
|
||||
LmdpdGh1Yi5ncmVnbmVhZ2xlLmxvZ2lud2luZG93Lkdsb2JhbFByZWZlcmVuY2VzPC9zdHJpbmc+
|
||||
CgkJCTxrZXk+UGF5bG9hZFR5cGU8L2tleT4KCQkJPHN0cmluZz4uR2xvYmFsUHJlZmVyZW5jZXM8
|
||||
L3N0cmluZz4KCQkJPGtleT5QYXlsb2FkVVVJRDwva2V5PgoJCQk8c3RyaW5nPjc2ZTNhMTUwLTZm
|
||||
MmMtNzI2MC0wNmQ1LTUwMjYxNWE1ODY4Njwvc3RyaW5nPgoJCQk8a2V5PlBheWxvYWRWZXJzaW9u
|
||||
PC9rZXk+CgkJCTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCTxrZXk+Y29tLmFwcGxlLmF1dG9sb2dv
|
||||
dXQuQXV0b0xvZ091dERlbGF5PC9rZXk+CgkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJPC9kaWN0
|
||||
PgoJPC9hcnJheT4KCTxrZXk+UGF5bG9hZERlc2NyaXB0aW9uPC9rZXk+Cgk8c3RyaW5nPkxvZ2lu
|
||||
d2luZG93IGNvbmZpZ3VyYXRpb24gc2V0dGluZ3M8L3N0cmluZz4KCTxrZXk+UGF5bG9hZERpc3Bs
|
||||
YXlOYW1lPC9rZXk+Cgk8c3RyaW5nPkxvZ2lud2luZG93IENvbmZpZ3VyYXRpb248L3N0cmluZz4K
|
||||
CTxrZXk+UGF5bG9hZElkZW50aWZpZXI8L2tleT4KCTxzdHJpbmc+Y29tLmdpdGh1Yi5ncmVnbmVh
|
||||
Z2xlLmxvZ2lud2luZG93PC9zdHJpbmc+Cgk8a2V5PlBheWxvYWRPcmdhbml6YXRpb248L2tleT4K
|
||||
CTxzdHJpbmc+PC9zdHJpbmc+Cgk8a2V5PlBheWxvYWRSZW1vdmFsRGlzYWxsb3dlZDwva2V5PgoJ
|
||||
PHRydWUvPgoJPGtleT5QYXlsb2FkU2NvcGU8L2tleT4KCTxzdHJpbmc+U3lzdGVtPC9zdHJpbmc+
|
||||
Cgk8a2V5PlBheWxvYWRUeXBlPC9rZXk+Cgk8c3RyaW5nPkNvbmZpZ3VyYXRpb248L3N0cmluZz4K
|
||||
CTxrZXk+UGF5bG9hZFVVSUQ8L2tleT4KCTxzdHJpbmc+MGRjMzE5YTAtYzMzMS0wMTMxLWVlYjUt
|
||||
MDAwYzI5NGFiODFiPC9zdHJpbmc+Cgk8a2V5PlBheWxvYWRWZXJzaW9uPC9rZXk+Cgk8aW50ZWdl
|
||||
cj4xPC9pbnRlZ2VyPgo8L2RpY3Q+CjwvcGxpc3Q+Cg==
|
||||
@@ -2,8 +2,8 @@ package workflow
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrExists is returned if a workflow already exists
|
||||
var ErrExists = errors.New("workflow already exists. each workflow must have a unique name")
|
||||
// ErrExists is returned when trying to add a resource which already exists
|
||||
var ErrExists = errors.New("resource already exists in the datastore")
|
||||
|
||||
// Workflow describes a workflow that a device will execute
|
||||
// A workflow contains a list of configuration profiles,
|
||||
|
||||
@@ -16,6 +16,15 @@ var (
|
||||
selectWorkflowsStmt = `SELECT workflow_uuid, name FROM profiles`
|
||||
)
|
||||
|
||||
// WrkflowUUID is a filter we can add as a parameter to narrow down the list of returned results
|
||||
type WrkflowUUID struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
func (p WrkflowUUID) where() string {
|
||||
return fmt.Sprintf("workflow_uuid = '%s'", p.UUID)
|
||||
}
|
||||
|
||||
// Create stores a new workflow in Postgres
|
||||
func (store pgStore) CreateWorkflow(wf *Workflow) (*Workflow, error) {
|
||||
err := store.QueryRow(createWorkflowStmt, wf.Name).Scan(&wf.UUID)
|
||||
|
||||
@@ -2,13 +2,8 @@ package workflow
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func TestDatastoreWorkflows(t *testing.T) {
|
||||
@@ -25,22 +20,40 @@ func (wf Workflow) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
return reflect.ValueOf(randomWorkflow)
|
||||
}
|
||||
|
||||
func randomString(strlen int) string {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
result := make([]byte, strlen)
|
||||
for i := 0; i < strlen; i++ {
|
||||
result[i] = chars[rand.Intn(len(chars))]
|
||||
}
|
||||
return string(result)
|
||||
var createWorkflowTests = []struct {
|
||||
in *Workflow
|
||||
shouldErr bool
|
||||
testErr error
|
||||
}{
|
||||
{
|
||||
in: &Workflow{},
|
||||
shouldErr: true,
|
||||
},
|
||||
{
|
||||
in: &Workflow{
|
||||
Name: "exampleWorkflow",
|
||||
},
|
||||
shouldErr: false,
|
||||
},
|
||||
{
|
||||
in: &Workflow{
|
||||
Name: "exampleWorkflow",
|
||||
},
|
||||
shouldErr: true,
|
||||
testErr: ErrExists,
|
||||
},
|
||||
{
|
||||
in: &Workflow{
|
||||
Name: "exampleWorkflowWithProfiles",
|
||||
Profiles: []Profile{
|
||||
Profile{UUID: "c7616875-df2d-4fe5-9c1e-0cb36c1ede8a"},
|
||||
},
|
||||
},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestNewDB(t *testing.T) {
|
||||
_ = datastore(t)
|
||||
defer teardown()
|
||||
}
|
||||
|
||||
func TestDatastoreCreate(t *testing.T) {
|
||||
func TestDatastoreCreateWorkflow(t *testing.T) {
|
||||
ds := datastore(t)
|
||||
defer teardown()
|
||||
|
||||
@@ -50,40 +63,7 @@ func TestDatastoreCreate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var createTests = []struct {
|
||||
in *Workflow
|
||||
shouldErr bool
|
||||
testErr error
|
||||
}{
|
||||
{
|
||||
in: &Workflow{},
|
||||
shouldErr: true,
|
||||
},
|
||||
{
|
||||
in: &Workflow{
|
||||
Name: "exampleWorkflow",
|
||||
},
|
||||
shouldErr: false,
|
||||
},
|
||||
{
|
||||
in: &Workflow{
|
||||
Name: "exampleWorkflow",
|
||||
},
|
||||
shouldErr: true,
|
||||
testErr: ErrExists,
|
||||
},
|
||||
{
|
||||
in: &Workflow{
|
||||
Name: "exampleWorkflowWithProfiles",
|
||||
Profiles: []Profile{
|
||||
Profile{UUID: "c7616875-df2d-4fe5-9c1e-0cb36c1ede8a"},
|
||||
},
|
||||
},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range createTests {
|
||||
for _, tt := range createWorkflowTests {
|
||||
_, err := ds.CreateWorkflow(tt.in)
|
||||
if !tt.shouldErr {
|
||||
checkErr(err)
|
||||
@@ -96,32 +76,3 @@ func TestDatastoreCreate(t *testing.T) {
|
||||
// check profiles
|
||||
}
|
||||
}
|
||||
|
||||
func datastore(t *testing.T) Datastore {
|
||||
logger := log.NewLogfmtLogger(os.Stderr)
|
||||
ds, err := NewDB("postgres", testConn, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return ds
|
||||
}
|
||||
|
||||
var (
|
||||
testConn = "user=micromdm password=micromdm dbname=micromdm sslmode=disable"
|
||||
)
|
||||
|
||||
func teardown() {
|
||||
db, err := sqlx.Open("postgres", testConn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
drop := `
|
||||
DROP TABLE IF EXISTS workflow_profile;
|
||||
DROP TABLE IF EXISTS workflow_workflow;
|
||||
DROP TABLE IF EXISTS profiles;
|
||||
DROP TABLE IF EXISTS workflows;
|
||||
`
|
||||
db.MustExec(drop)
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user