mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-12 21:35:40 +08:00
move profile datastore into workflow datastore instead
This commit is contained in:
@@ -1,135 +0,0 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq" // postgres driver
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrExists is returned if a profile already exists
|
||||
var ErrExists = errors.New("profile already exists. each profile must have a unique identifier")
|
||||
|
||||
// Datastore manages profiles in a database
|
||||
type Datastore interface {
|
||||
// Add adds a profile to the datastore,
|
||||
// If a profile already exists, an error will be returned
|
||||
Add(pr *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")}
|
||||
Profiles(params ...interface{}) ([]Profile, error)
|
||||
}
|
||||
|
||||
// whereer is for building args passed into Profiles()
|
||||
type whereer interface {
|
||||
where() string
|
||||
}
|
||||
|
||||
// Identifier is a PayloadIdentifier filter which can be passed as a param to Profiles()
|
||||
type Identifier struct{ PayloadIdentifier string }
|
||||
|
||||
func (p Identifier) where() string {
|
||||
return fmt.Sprintf("identifier='%s'", p.PayloadIdentifier)
|
||||
}
|
||||
|
||||
// UUID is a Profile UUID filter which can be passed as a param to Profiles()
|
||||
type UUID struct{ UUID string }
|
||||
|
||||
func (p UUID) where() string {
|
||||
return fmt.Sprintf("profile_uuid='%s'", p.UUID)
|
||||
}
|
||||
|
||||
type pgStore struct {
|
||||
*sqlx.DB
|
||||
}
|
||||
|
||||
func (store pgStore) Add(prf *Profile) (*Profile, error) {
|
||||
err := store.QueryRow(addProfileStmt, prf.PayloadIdentifier, prf.ProfileData).Scan(&prf.UUID)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrExists
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore add profile")
|
||||
}
|
||||
return prf, 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)
|
||||
}
|
||||
|
||||
var profiles []Profile
|
||||
err := store.Select(&profiles, stmt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore Profiles")
|
||||
}
|
||||
|
||||
return profiles, nil
|
||||
|
||||
}
|
||||
|
||||
//NewDB creates a Datastore
|
||||
func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) {
|
||||
switch driver {
|
||||
case "postgres":
|
||||
db, err := sqlx.Open(driver, conn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "profile datastore")
|
||||
}
|
||||
var dbError error
|
||||
maxAttempts := 20
|
||||
for attempts := 1; attempts <= maxAttempts; attempts++ {
|
||||
dbError = db.Ping()
|
||||
if dbError == nil {
|
||||
break
|
||||
}
|
||||
logger.Log("msg", fmt.Sprintf("could not connect to postgres: %v", dbError))
|
||||
time.Sleep(time.Duration(attempts) * time.Second)
|
||||
}
|
||||
if dbError != nil {
|
||||
return nil, errors.Wrap(dbError, "profile datastore")
|
||||
}
|
||||
migrate(db)
|
||||
return pgStore{DB: db}, nil
|
||||
default:
|
||||
return nil, errors.New("unknown driver")
|
||||
}
|
||||
}
|
||||
|
||||
// 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`
|
||||
)
|
||||
|
||||
func migrate(db *sqlx.DB) {
|
||||
schema := `
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
profile_uuid uuid PRIMARY KEY
|
||||
DEFAULT uuid_generate_v4(),
|
||||
identifier text UNIQUE NOT NULL,
|
||||
profile_data bytea
|
||||
);`
|
||||
|
||||
db.MustExec(schema)
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func TestNewDB(t *testing.T) {
|
||||
defer teardown()
|
||||
logger := log.NewLogfmtLogger(os.Stderr)
|
||||
_, err := NewDB("postgres", testConn, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdd generates a bunch of new profiles
|
||||
// and adds them to the database.
|
||||
// The test checks that the profile stored is the same as the one returned.
|
||||
func TestDatastoreAdd(t *testing.T) {
|
||||
ds := datastore(t)
|
||||
defer teardown()
|
||||
|
||||
assertion := func(pf Profile) bool {
|
||||
newPrf, err := ds.Add(&pf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return false
|
||||
}
|
||||
if newPrf.UUID == "" || newPrf.PayloadIdentifier != pf.PayloadIdentifier {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if err := quick.Check(assertion, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatastoreProfiles(t *testing.T) {
|
||||
ds := datastore(t)
|
||||
defer teardown()
|
||||
|
||||
// test with empty table
|
||||
testReturnNoArgs(t, ds, 0)
|
||||
|
||||
// insert some profiles and retrieve them
|
||||
var profileCount = 5
|
||||
addRandomProfiles(profileCount, ds, t)
|
||||
profiles, err := ds.Profiles()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
returnedCount := len(profiles)
|
||||
if returnedCount != profileCount {
|
||||
t.Fatal("expected", profileCount, "got", returnedCount)
|
||||
}
|
||||
|
||||
// build data for remaining tests
|
||||
var allIdentifiers []string
|
||||
var allUUIDs []string
|
||||
for _, p := range profiles {
|
||||
allIdentifiers = append(allIdentifiers, p.PayloadIdentifier)
|
||||
allUUIDs = append(allUUIDs, p.UUID)
|
||||
}
|
||||
|
||||
// return everything
|
||||
testReturnNoArgs(t, ds, profileCount)
|
||||
// test nonexisting profile
|
||||
testReturnNone(t, ds)
|
||||
// test returning a unique id
|
||||
testReturnOneByIdentifier(t, ds, allIdentifiers[0])
|
||||
// test returning a unique profile by UUID
|
||||
testReturnOneByUUID(t, ds, allUUIDs[0])
|
||||
|
||||
}
|
||||
func testReturnNoArgs(t *testing.T, ds Datastore, expectCount int) {
|
||||
profiles, err := ds.Profiles()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
returnedCount := len(profiles)
|
||||
if returnedCount != expectCount {
|
||||
t.Fatal("expected", expectCount, "got", returnedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func testReturnNone(t *testing.T, ds Datastore) {
|
||||
identifier := "com.example.does.not.exist"
|
||||
profiles, err := ds.Profiles(Identifier{identifier})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
returnedCount := len(profiles)
|
||||
if returnedCount != 0 {
|
||||
t.Fatal("expected", 0, "got", returnedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func testReturnOneByIdentifier(t *testing.T, ds Datastore, id string) {
|
||||
profiles, err := ds.Profiles(Identifier{id})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
returnedCount := len(profiles)
|
||||
if returnedCount != 1 {
|
||||
t.Fatal("expected", 1, "got", returnedCount)
|
||||
}
|
||||
|
||||
returnedIdentifier := profiles[0].PayloadIdentifier
|
||||
if returnedIdentifier != id {
|
||||
t.Fatal("expected", id, "got", returnedIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
func testReturnOneByUUID(t *testing.T, ds Datastore, uuid string) {
|
||||
profiles, err := ds.Profiles(UUID{uuid})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
returnedCount := len(profiles)
|
||||
if returnedCount != 1 {
|
||||
t.Fatal("expected", 1, "got", returnedCount)
|
||||
}
|
||||
|
||||
returnedUUID := profiles[0].UUID
|
||||
if returnedUUID != uuid {
|
||||
t.Fatal("expected", uuid, "got", returnedUUID)
|
||||
}
|
||||
}
|
||||
|
||||
func addRandomProfiles(num int, ds Datastore, t *testing.T) {
|
||||
for i := 1; i <= num; i++ {
|
||||
pf := randomProfile()
|
||||
_, err := ds.Add(&pf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
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{}
|
||||
}
|
||||
|
||||
// implement quick.Generator for Profile
|
||||
func (pf Profile) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
a := RandomString(16)
|
||||
b := RandomString(16)
|
||||
c := RandomString(16)
|
||||
randomIdnetifier := fmt.Sprintf("%v.%v.%v", a, b, c)
|
||||
randomProfile := Profile{
|
||||
PayloadIdentifier: randomIdnetifier,
|
||||
ProfileData: `PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KICAgIDxrZXk+UGF5bG9hZENvbnRlbnQ8L2tleT4KICAgIDxhcnJheT4KICAgICAgICA8ZGljdD4KICAgICAgICAgICAgPGtleT5QYXlsb2FkQ29udGVudDwva2V5PgogICAgICAgICAgICA8ZGljdD4KICAgICAgICAgICAgICAgIDxrZXk+Y29tLmFwcGxlLlNldHVwQXNzaXN0YW50PC9rZXk+CiAgICAgICAgICAgICAgICA8ZGljdD4KICAgICAgICAgICAgICAgICAgICA8a2V5PlNldC1PbmNlPC9rZXk+CiAgICAgICAgICAgICAgICAgICAgPGFycmF5PgogICAgICAgICAgICAgICAgICAgICAgICA8ZGljdD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxrZXk+bWN4X2RhdGFfdGltZXN0YW1wPC9rZXk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGF0ZT4yMDE0LTEwLTI5VDE3OjIwOjEwWjwvZGF0ZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxrZXk+bWN4X3ByZWZlcmVuY2Vfc2V0dGluZ3M8L2tleT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaWN0PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxrZXk+RGlkU2VlQ2xvdWRTZXR1cDwva2V5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cnVlLz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8a2V5Pkdlc3R1cmVNb3ZpZVNlZW48L2tleT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3RyaW5nPm5vbmU8L3N0cmluZz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8a2V5Pkxhc3RTZWVuQ2xvdWRQcm9kdWN0VmVyc2lvbjwva2V5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzdHJpbmc+MTAuMTEuMjwvc3RyaW5nPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxrZXk+TGFzdFNlZW5CdWRkeUJ1aWxkVmVyc2lvbjwva2V5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzdHJpbmc+MTVDNTA8L3N0cmluZz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGljdD4KICAgICAgICAgICAgICAgICAgICAgICAgPC9kaWN0PgogICAgICAgICAgICAgICAgICAgIDwvYXJyYXk+CiAgICAgICAgICAgICAgICA8L2RpY3Q+CiAgICAgICAgICAgIDwvZGljdD4KICAgICAgICAgICAgPGtleT5QYXlsb2FkRW5hYmxlZDwva2V5PgogICAgICAgICAgICA8dHJ1ZS8+CiAgICAgICAgICAgIDxrZXk+UGF5bG9hZElkZW50aWZpZXI8L2tleT4KICAgICAgICAgICAgPHN0cmluZz5lZHUucHJhdHQuc3VwcHJlc3NfaWNsb3VkX2Fzc3Q8L3N0cmluZz4KICAgICAgICAgICAgPGtleT5QYXlsb2FkVHlwZTwva2V5PgogICAgICAgICAgICA8c3RyaW5nPmNvbS5hcHBsZS5NYW5hZ2VkQ2xpZW50LnByZWZlcmVuY2VzPC9zdHJpbmc+CiAgICAgICAgICAgIDxrZXk+UGF5bG9hZFVVSUQ8L2tleT4KICAgICAgICAgICAgPHN0cmluZz41ZTEwZjM0OC05MjNiLTQzOGEtOWI4Ny1mYTk3OGU4NmUxMWE8L3N0cmluZz4KICAgICAgICAgICAgPGtleT5QYXlsb2FkVmVyc2lvbjwva2V5PgogICAgICAgICAgICA8aW50ZWdlcj4xPC9pbnRlZ2VyPgogICAgICAgIDwvZGljdD4KICAgIDwvYXJyYXk+CiAgICA8a2V5PlBheWxvYWREZXNjcmlwdGlvbjwva2V5PgogICAgPHN0cmluZz5Db25maWd1cmVzIGNvbS5hcHBsZS5TZXR1cEFzc2lzdGFudDwvc3RyaW5nPgogICAgPGtleT5QYXlsb2FkRGlzcGxheU5hbWU8L2tleT4KICAgIDxzdHJpbmc+aUNsb3VkIFNldHVwQXNzaXN0YW50IENvbmZpZ3VyYXRpb248L3N0cmluZz4KICAgIDxrZXk+UGF5bG9hZElkZW50aWZpZXI8L2tleT4KICAgIDxzdHJpbmc+Y29tLmdpdGh1Yi5ncmVnbmVhZ2xlLnN1cHByZXNzX2ljbG91ZF9hc3N0PC9zdHJpbmc+CiAgICA8a2V5PlBheWxvYWRPcmdhbml6YXRpb248L2tleT4KICAgIDxzdHJpbmc+PC9zdHJpbmc+CiAgICA8a2V5PlBheWxvYWRSZW1vdmFsRGlzYWxsb3dlZDwva2V5PgogICAgPGZhbHNlLz4KICAgIDxrZXk+UGF5bG9hZFNjb3BlPC9rZXk+CiAgICA8c3RyaW5nPlN5c3RlbTwvc3RyaW5nPgogICAgPGtleT5QYXlsb2FkVHlwZTwva2V5PgogICAgPHN0cmluZz5Db25maWd1cmF0aW9uPC9zdHJpbmc+CiAgICA8a2V5PlBheWxvYWRVVUlEPC9rZXk+CiAgICA8c3RyaW5nPmU4MWY1MWMyLTExODAtNGRlMC05NGNkLTMxNTNhYTQxMzg3Njwvc3RyaW5nPgogICAgPGtleT5QYXlsb2FkVmVyc2lvbjwva2V5PgogICAgPGludGVnZXI+MTwvaW50ZWdlcj4KPC9kaWN0Pgo8L3BsaXN0Pgo=`,
|
||||
}
|
||||
return reflect.ValueOf(randomProfile)
|
||||
}
|
||||
|
||||
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 (
|
||||
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 setup() {
|
||||
db, err := sqlx.Open("postgres", testConn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
migrate(db)
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
func teardown() {
|
||||
db, err := sqlx.Open("postgres", testConn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
drop := `
|
||||
DROP TABLE IF EXISTS profiles;
|
||||
`
|
||||
db.MustExec(drop)
|
||||
defer db.Close()
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
@@ -12,113 +10,32 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrExists is returned if a workflow already exists
|
||||
var ErrExists = errors.New("workflow already exists. each workflow must have a unique name")
|
||||
|
||||
// Profile is configuration profile in a workflow
|
||||
type Profile struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
// Workflow describes a workflow that a device will execute
|
||||
// A workflow contains a list of configuration profiles,
|
||||
// Applications and included workflows
|
||||
type Workflow struct {
|
||||
UUID string `json:"uuid" db:"workflow_uuid"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Profiles []Profile `json:"profiles"`
|
||||
// Applications []application
|
||||
// IncludedWorkflows []Workflow
|
||||
}
|
||||
|
||||
// Datastore manages interactions of workflows in a database
|
||||
type Datastore interface {
|
||||
// Create adds a new workflow to the datastore
|
||||
Create(wf *Workflow) (*Workflow, error)
|
||||
CreateWorkflow(wf *Workflow) (*Workflow, error)
|
||||
|
||||
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)
|
||||
|
||||
// Profiles can query the datastore for one or more profiles
|
||||
// and accepts one or more params as filters
|
||||
// Example: Profiles(Identifier{"com.example.id")}
|
||||
Profiles(params ...interface{}) ([]Profile, error)
|
||||
}
|
||||
|
||||
type pgStore struct {
|
||||
*sqlx.DB
|
||||
}
|
||||
|
||||
// Create stores a new workflow in Postgres
|
||||
func (store pgStore) Create(wf *Workflow) (*Workflow, error) {
|
||||
err := store.QueryRow(createWorkflowStmt, wf.Name).Scan(&wf.UUID)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrExists
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore create workflow")
|
||||
}
|
||||
|
||||
profiles := wf.Profiles
|
||||
if err := store.addProfiles(wf.UUID, profiles...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wf, nil
|
||||
}
|
||||
|
||||
func (store pgStore) addProfiles(wfUUID string, profiles ...Profile) error {
|
||||
if len(profiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, prf := range profiles {
|
||||
if err := store.addProfile(wfUUID, prf.UUID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store pgStore) addProfile(wfUUID, pfUUID string) error {
|
||||
addProfileStmt := `INSERT INTO workflow_profile (workflow_uuid, profile_uuid) VALUES ($1, $2)
|
||||
ON CONFLICT ON CONSTRAINT workflow_profile_pkey DO NOTHING;`
|
||||
|
||||
_, err := store.Exec(addProfileStmt, wfUUID, pfUUID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "pgStore add profile to workflow")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store pgStore) Workflows(params ...interface{}) ([]Workflow, error) {
|
||||
stmt := selectWorkflowsStmt
|
||||
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", selectWorkflowsStmt, whereFilter)
|
||||
}
|
||||
|
||||
var workflows []Workflow
|
||||
err := store.Select(&workflows, stmt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore Workflows")
|
||||
}
|
||||
return workflows, nil
|
||||
}
|
||||
|
||||
// whereer is for building args passed into Profiles()
|
||||
// whereer is for building args passed into a method which finds resources
|
||||
type whereer interface {
|
||||
where() string
|
||||
}
|
||||
|
||||
// sql statements
|
||||
var (
|
||||
createWorkflowStmt = `INSERT INTO workflows (name) VALUES ($1)
|
||||
ON CONFLICT ON CONSTRAINT workflows_name_key DO NOTHING
|
||||
RETURNING workflow_uuid;`
|
||||
selectWorkflowsStmt = `SELECT workflow_uuid, name FROM profiles`
|
||||
)
|
||||
|
||||
//NewDB creates a Datastore
|
||||
func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) {
|
||||
switch driver {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package profile
|
||||
package workflow
|
||||
|
||||
// Profile is an Apple Configuration profile
|
||||
type Profile struct {
|
||||
52
workflow/profile_store.go
Normal file
52
workflow/profile_store.go
Normal file
@@ -0,0 +1,52 @@
|
||||
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`
|
||||
)
|
||||
|
||||
func (store pgStore) CreateProfile(prf *Profile) (*Profile, error) {
|
||||
err := store.QueryRow(addProfileStmt, prf.PayloadIdentifier, prf.ProfileData).Scan(&prf.UUID)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrExists
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore add profile")
|
||||
}
|
||||
return prf, 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)
|
||||
}
|
||||
|
||||
var profiles []Profile
|
||||
err := store.Select(&profiles, stmt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore Profiles")
|
||||
}
|
||||
|
||||
return profiles, nil
|
||||
|
||||
}
|
||||
1
workflow/profile_store_test.go
Normal file
1
workflow/profile_store_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package workflow
|
||||
17
workflow/workflow.go
Normal file
17
workflow/workflow.go
Normal file
@@ -0,0 +1,17 @@
|
||||
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")
|
||||
|
||||
// Workflow describes a workflow that a device will execute
|
||||
// A workflow contains a list of configuration profiles,
|
||||
// Applications and included workflows
|
||||
type Workflow struct {
|
||||
UUID string `json:"uuid" db:"workflow_uuid"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Profiles []Profile `json:"profiles"`
|
||||
// Applications []application
|
||||
// IncludedWorkflows []Workflow
|
||||
}
|
||||
81
workflow/workflow_store.go
Normal file
81
workflow/workflow_store.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// sql statements
|
||||
var (
|
||||
createWorkflowStmt = `INSERT INTO workflows (name) VALUES ($1)
|
||||
ON CONFLICT ON CONSTRAINT workflows_name_key DO NOTHING
|
||||
RETURNING workflow_uuid;`
|
||||
selectWorkflowsStmt = `SELECT workflow_uuid, name FROM profiles`
|
||||
)
|
||||
|
||||
// Create stores a new workflow in Postgres
|
||||
func (store pgStore) CreateWorkflow(wf *Workflow) (*Workflow, error) {
|
||||
err := store.QueryRow(createWorkflowStmt, wf.Name).Scan(&wf.UUID)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrExists
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore create workflow")
|
||||
}
|
||||
|
||||
profiles := wf.Profiles
|
||||
if err := store.addProfiles(wf.UUID, profiles...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wf, nil
|
||||
}
|
||||
|
||||
func (store pgStore) addProfiles(wfUUID string, profiles ...Profile) error {
|
||||
if len(profiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, prf := range profiles {
|
||||
if err := store.addProfile(wfUUID, prf.UUID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store pgStore) addProfile(wfUUID, pfUUID string) error {
|
||||
addProfileStmt := `INSERT INTO workflow_profile (workflow_uuid, profile_uuid) VALUES ($1, $2)
|
||||
ON CONFLICT ON CONSTRAINT workflow_profile_pkey DO NOTHING;`
|
||||
|
||||
_, err := store.Exec(addProfileStmt, wfUUID, pfUUID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "pgStore add profile to workflow")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store pgStore) Workflows(params ...interface{}) ([]Workflow, error) {
|
||||
stmt := selectWorkflowsStmt
|
||||
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", selectWorkflowsStmt, whereFilter)
|
||||
}
|
||||
|
||||
var workflows []Workflow
|
||||
err := store.Select(&workflows, stmt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore Workflows")
|
||||
}
|
||||
return workflows, nil
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func TestDatastoreCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range createTests {
|
||||
_, err := ds.Create(tt.in)
|
||||
_, err := ds.CreateWorkflow(tt.in)
|
||||
if !tt.shouldErr {
|
||||
checkErr(err)
|
||||
}
|
||||
Reference in New Issue
Block a user