add basic profile and workflow

This commit is contained in:
Victor Vrantchan
2016-05-02 16:44:33 -04:00
parent 4133e19bfa
commit 3db7195ee9
3 changed files with 359 additions and 2 deletions

231
workflow/workflow.go Normal file
View File

@@ -0,0 +1,231 @@
// Package workflow manages device workflows
package workflow
import (
"errors"
"fmt"
"os"
"time"
"github.com/go-kit/kit/log"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // postgres driver
"github.com/micromdm/micromdm/profile"
"golang.org/x/net/context"
)
// ErrNoRowsModified is returned if insert didn't produce results
var ErrNoRowsModified = errors.New("DB: No rows affected")
type application struct {
ManagementFlags int
ManifestURL string
}
// Workflow is a device workflow
type Workflow struct {
UUID string `db:"workflow_uuid"`
Name string `db:"name"`
Profiles []profile.Profile
Applications []application
IncludedWorkflows []Workflow
}
// Datastore manages interactions of workflows in a database
type Datastore interface {
CreateWorkflow(string) (*Workflow, error)
AddProfile(string, string) error
RemoveProfile(string, string) error
GetWorkflows() ([]Workflow, error)
}
type pgDatastore struct {
*sqlx.DB
}
func (db pgDatastore) CreateWorkflow(name string) (*Workflow, error) {
upsert := `INSERT INTO workflows
(name)
VALUES ($1)
ON CONFLICT ON CONSTRAINT workflows_name_key
DO NOTHING;`
result, err := db.Exec(
upsert,
name,
)
if err != nil {
return nil, err
}
if res, _ := result.RowsAffected(); res == 0 {
return nil, ErrNoRowsModified
}
var wf Workflow
err = db.Get(&wf, "SELECT * FROM workflows WHERE name=$1", name)
if err != nil {
return nil, err
}
return &wf, nil
}
func (db pgDatastore) RemoveProfile(wfUUID, pfUUID string) error {
remove := `DELETE FROM workflow_profile
WHERE workflow_uuid=$1 AND profile_uuid=$2;`
result, err := db.Exec(
remove,
wfUUID,
pfUUID,
)
if err != nil {
return err
}
if res, _ := result.RowsAffected(); res == 0 {
return ErrNoRowsModified
}
return nil
}
func (db pgDatastore) GetWorkflows() ([]Workflow, error) {
var workflows []Workflow
err := db.Select(&workflows, "SELECT * FROM workflows")
if err != nil {
return nil, err
}
var withProfiles []Workflow
for _, wf := range workflows {
profiles, err := db.getProfilesForWorkflow(wf.UUID)
if err != nil {
return nil, err
}
wf.Profiles = profiles
withProfiles = append(withProfiles, wf)
}
return withProfiles, nil
}
func (db pgDatastore) getProfilesForWorkflow(uuid string) ([]profile.Profile, error) {
var profileUUIDs []string
err := db.Select(&profileUUIDs, "SELECT profile_uuid FROM workflow_profile WHERE workflow_uuid=$1", uuid)
if err != nil {
return nil, err
}
var profiles []profile.Profile
for _, id := range profileUUIDs {
var pf profile.Profile
err := db.Get(&pf, "SELECT * FROM profiles WHERE profile_uuid=$1", id)
if err != nil {
return nil, err
}
profiles = append(profiles, pf)
}
return profiles, nil
}
func (db pgDatastore) AddProfile(wfUUID, pfUUID string) error {
update := `INSERT INTO workflow_profile
(workflow_uuid, profile_uuid)
VALUES ($1, $2);`
result, err := db.Exec(
update,
wfUUID,
pfUUID,
)
if err != nil {
return err
}
if res, _ := result.RowsAffected(); res == 0 {
return ErrNoRowsModified
}
return nil
}
// Logger adds a logger to the database config
func Logger(logger log.Logger) func(*config) error {
return func(c *config) error {
c.logger = logger
return nil
}
}
type config struct {
context context.Context
logger log.Logger
}
// NewDB creates a new databases connection
func NewDB(driver, conn string, options ...func(*config) error) Datastore {
conf := &config{}
defaultLogger := log.NewLogfmtLogger(os.Stderr)
for _, option := range options {
if err := option(conf); err != nil {
defaultLogger.Log("err", err)
os.Exit(1)
}
}
if conf.logger == nil {
conf.logger = defaultLogger
}
switch driver {
case "postgres":
db, err := sqlx.Open(driver, conn)
if err != nil {
conf.logger.Log("err", err)
os.Exit(1)
}
var dbError error
maxAttempts := 20
for attempts := 1; attempts <= maxAttempts; attempts++ {
dbError = db.Ping()
if dbError == nil {
break
}
conf.logger.Log("msg", fmt.Sprintf("could not connect to postgres: %v", dbError))
time.Sleep(time.Duration(attempts) * time.Second)
}
if dbError != nil {
conf.logger.Log("err", dbError)
os.Exit(1)
}
migrate(db)
// TODO: configurable with default
db.SetMaxOpenConns(5)
store := pgDatastore{db}
return store
default:
conf.logger.Log("err", "unknown driver")
os.Exit(1)
return nil
}
}
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
);
CREATE TABLE IF NOT EXISTS workflows (
workflow_uuid uuid PRIMARY KEY
DEFAULT uuid_generate_v4(),
name text UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS workflow_profile (
workflow_uuid uuid REFERENCES workflows,
profile_uuid uuid REFERENCES profiles,
PRIMARY KEY (workflow_uuid, profile_uuid)
);
CREATE TABLE IF NOT EXISTS workflow_workflow (
workflow_uuid uuid REFERENCES workflows,
included_workflow_uuid uuid REFERENCES workflows(workflow_uuid),
PRIMARY KEY (workflow_uuid, included_workflow_uuid)
);`
db.MustExec(schema)
}

View File

@@ -0,0 +1,125 @@
package workflow
import (
"fmt"
"log"
"os"
"testing"
"github.com/jmoiron/sqlx"
"github.com/micromdm/micromdm/profile"
)
var testConn = "user=micromdm password=micromdm dbname=micromdm sslmode=disable"
func newDB(driver string) *sqlx.DB {
db, err := sqlx.Open(driver, testConn)
if err != nil {
panic(err)
}
return db
}
func TestMain(m *testing.M) {
db := newDB("postgres")
teardown(db)
retCode := m.Run()
teardown(db)
// call with result of m.Run()
os.Exit(retCode)
}
func TestNewDBConnection(t *testing.T) {
pg := newDB("postgres")
checkExists := `SELECT * from information_schema.tables WHERE table_name = 'workflows'`
t.Log("workflow: testing new postgres connection")
_ = NewDB("postgres", testConn)
_, err := pg.Query(checkExists)
if err != nil {
t.Fatal(err)
}
}
func TestCreateWorkflow(t *testing.T) {
store := NewDB("postgres", testConn)
db := newDB("postgres")
db.MustExec(`INSERT INTO profiles (identifier) VALUES ($1);`, "com.micromdm.test")
db.MustExec(`INSERT INTO profiles (identifier) VALUES ($1);`, "com.micromdm.test2")
var pf profile.Profile
err := db.Get(&pf, "SELECT * FROM profiles WHERE identifier=$1", "com.micromdm.test")
if err != nil {
t.Fatal(err)
}
wf, err := store.CreateWorkflow("test_workflow")
if err != nil {
t.Fatal(err)
}
err = store.AddProfile(wf.UUID, pf.UUID)
if err != nil {
t.Fatal(err)
}
err = db.Get(&pf, "SELECT * FROM profiles WHERE identifier=$1", "com.micromdm.test2")
if err != nil {
t.Fatal(err)
}
err = store.AddProfile(wf.UUID, pf.UUID)
if err != nil {
t.Fatal(err)
}
}
func TestGetWorkflows(t *testing.T) {
store := NewDB("postgres", testConn)
workflows, err := store.GetWorkflows()
if err != nil {
t.Fatal(err)
}
if len(workflows) == 0 {
t.Fatal("should have at least one workflow")
}
if len(workflows[0].Profiles) == 0 {
t.Fatal("should have at least one profile")
}
fmt.Printf("%+v", workflows[0])
}
func TestRemoveProfile(t *testing.T) {
store := NewDB("postgres", testConn)
db := newDB("postgres")
var pf profile.Profile
err := db.Get(&pf, "SELECT * FROM profiles WHERE identifier=$1", "com.micromdm.test")
if err != nil {
t.Fatal(err)
}
wf, err := store.CreateWorkflow("test_workflow_two")
if err != nil {
t.Fatal(err)
}
err = store.AddProfile(wf.UUID, pf.UUID)
if err != nil {
t.Fatal(err)
}
err = store.RemoveProfile(wf.UUID, pf.UUID)
if err != nil {
t.Fatal(err)
}
}
func teardown(db *sqlx.DB) {
log.Println("workflow: dropping test tables")
drop := `
DROP TABLE IF EXISTS workflow_workflow;
DROP TABLE IF EXISTS workflow_profile;
DROP TABLE IF EXISTS workflows;
DROP TABLE IF EXISTS profiles;
`
db.MustExec(drop)
}