Blueprints apply and list/get (#145)

This commit is contained in:
Jesse Peterson
2017-04-26 09:56:32 -07:00
committed by GitHub
parent 2d90d3b69b
commit 6d5fd5e97a
10 changed files with 224 additions and 19 deletions

View File

@@ -64,6 +64,9 @@ func (db *DB) List() ([]Blueprint, error) {
}
func (db *DB) Save(bp *Blueprint) error {
if bp.Name == "" || bp.UUID == "" {
return errors.New("cannot Save: blueprint must have Name and UUID")
}
tx, err := db.DB.Begin(true)
if err != nil {
return errors.Wrap(err, "begin transaction")

View File

@@ -2,6 +2,7 @@ package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
@@ -11,7 +12,9 @@ import (
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/micromdm/micromdm/blueprint"
"github.com/micromdm/micromdm/core/apply"
uuid "github.com/satori/go.uuid"
)
type applyCommand struct {
@@ -75,10 +78,60 @@ Examples:
func (cmd *applyCommand) applyBlueprint(args []string) error {
flagset := flag.NewFlagSet("blueprints", flag.ExitOnError)
var (
flBlueprintPath = flagset.String("f", "", "filename of blueprint JSON to apply")
flNewBlueprintPath = flagset.String("generate-blueprint", "", "filename of new template blueprint JSON to create")
)
flagset.Usage = usageFor(flagset, "mdmctl apply blueprints [flags]")
if err := flagset.Parse(args); err != nil {
return err
}
if *flBlueprintPath == "" && *flNewBlueprintPath == "" {
return errors.New("must provide -f or -generate-blueprint parameter")
}
if *flBlueprintPath != "" {
if _, err := os.Stat(*flBlueprintPath); os.IsNotExist(err) {
return err
}
jsonBytes, err := ioutil.ReadFile(*flBlueprintPath)
if err != nil {
return err
}
var blpt blueprint.Blueprint
err = json.Unmarshal(jsonBytes, &blpt)
if err != nil {
return err
}
ctx := context.Background()
err = cmd.applysvc.ApplyBlueprint(ctx, &blpt)
if err != nil {
return err
}
fmt.Println("applied blueprint", *flBlueprintPath)
return nil
}
if *flNewBlueprintPath != "" {
newBlueprintFile, err := os.Create(*flNewBlueprintPath)
if err != nil {
return err
}
defer newBlueprintFile.Close()
newBlueprint := new(blueprint.Blueprint)
newBlueprint.Name = "exampleName"
newBlueprint.UUID = uuid.NewV4().String()
newBlueprint.ApplicationURLs = []string{cmd.config.ServerURL + "repo/exampleAppManifest.plist"}
newBlueprint.Profiles = []blueprint.Mobileconfig{blueprint.Mobileconfig([]byte("this should be a configuration profile"))}
enc := json.NewEncoder(newBlueprintFile)
enc.SetIndent("", " ")
err = enc.Encode(newBlueprint)
if err != nil {
return err
}
fmt.Println("wrote", *flNewBlueprintPath)
}
return nil
}

View File

@@ -53,6 +53,8 @@ func (cmd *getCommand) Run(args []string) error {
run = cmd.getDevices
case "dep-tokens":
run = cmd.getDepTokens
case "blueprints":
run = cmd.getBlueprints
default:
cmd.Usage()
os.Exit(1)
@@ -193,3 +195,59 @@ func WritePEMCertificateFile(cert *x509.Certificate, path string) error {
Bytes: cert.Raw,
})
}
func (cmd *getCommand) getBlueprints(args []string) error {
flagset := flag.NewFlagSet("blueprints", flag.ExitOnError)
var (
flBlueprintName = flagset.String("name", "", "name of blueprint")
flJSONName = flagset.String("json", "", "file name of JSON to save for a single result")
)
flagset.Usage = usageFor(flagset, "mdmctl get blueprints [flags]")
if err := flagset.Parse(args); err != nil {
return err
}
ctx := context.Background()
blueprints, err := cmd.list.GetBlueprints(ctx, list.GetBlueprintsOption{FilterName: *flBlueprintName})
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprintf(w, "Name\tUUID\tManifests\tProfiles\n")
for _, bp := range blueprints {
fmt.Fprintf(
w,
"%s\t%s\t%d\t%d\n",
bp.Name,
bp.UUID,
len(bp.ApplicationURLs),
len(bp.Profiles),
)
}
w.Flush()
if *flJSONName != "" && len(blueprints) > 0 {
bp := blueprints[0]
bpFile, err := os.Create(*flJSONName)
if err != nil {
return err
}
defer bpFile.Close()
enc := json.NewEncoder(bpFile)
enc.SetIndent("", " ")
err = enc.Encode(bp)
if err != nil {
return err
}
fmt.Printf("\nWrote Blueprint to: %s\n", *flJSONName)
if len(blueprints) > 1 {
fmt.Println("WARNING: more than one Blueprint returned; only saved first")
}
}
return nil
}

View File

@@ -67,3 +67,4 @@ type depTokensResponse struct {
}
func (r depTokensResponse) error() error { return r.Err }
func (r blueprintResponse) error() error { return r.Err }

View File

@@ -9,7 +9,6 @@ import (
"net/http"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/micromdm/micromdm/blueprint"
)
type HTTPHandlers struct {
@@ -44,14 +43,11 @@ func decodeDEPTokensRequest(ctx context.Context, r *http.Request) (interface{},
}
func decodeBlueprintRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var bp blueprint.Blueprint
if err := json.NewDecoder(r.Body).Decode(&bp); err != nil {
var bpReq blueprintRequest
if err := json.NewDecoder(r.Body).Decode(&bpReq); err != nil {
return nil, err
}
req := blueprintRequest{
Blueprint: &bp,
}
return req, nil
return bpReq, nil
}
type errorWrapper struct {

View File

@@ -36,10 +36,20 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
opts...,
).Endpoint()
}
var getBlueprintsEndpoint endpoint.Endpoint
{
getBlueprintsEndpoint = httptransport.NewClient(
"GET",
copyURL(u, "/v1/blueprints"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeGetBlueprintsResponse,
).Endpoint()
}
return Endpoints{
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: getDEPTokensEndpoint,
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: getDEPTokensEndpoint,
GetBlueprintsEndpoint: getBlueprintsEndpoint,
}, nil
}

View File

@@ -5,11 +5,13 @@ import (
"time"
"github.com/go-kit/kit/endpoint"
"github.com/micromdm/micromdm/blueprint"
)
type Endpoints struct {
ListDevicesEndpoint endpoint.Endpoint
GetDEPTokensEndpoint endpoint.Endpoint
ListDevicesEndpoint endpoint.Endpoint
GetDEPTokensEndpoint endpoint.Endpoint
GetBlueprintsEndpoint endpoint.Endpoint
}
func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]DeviceDTO, error) {
@@ -29,6 +31,15 @@ func (e Endpoints) GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error)
return resp.(depTokenResponse).DEPTokens, resp.(depTokenResponse).DEPPubKey, nil
}
func (e Endpoints) GetBlueprints(ctx context.Context, opt GetBlueprintsOption) ([]blueprint.Blueprint, error) {
request := blueprintsRequest{opt}
response, err := e.GetBlueprintsEndpoint(ctx, request.Opts)
if err != nil {
return nil, err
}
return response.(blueprintsResponse).Blueprints, response.(blueprintsResponse).Err
}
func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(devicesRequest)
@@ -51,6 +62,17 @@ func MakeGetDEPTokensEndpoint(svc Service) endpoint.Endpoint {
}
}
func MakeGetBlueprintsEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(blueprintsRequest)
blueprints, err := svc.GetBlueprints(ctx, req.Opts)
return blueprintsResponse{
Blueprints: blueprints,
Err: err,
}, nil
}
}
type DeviceDTO struct {
SerialNumber string `json:"serial_number"`
UDID string `json:"udid"`
@@ -77,3 +99,11 @@ type depTokenResponse struct {
DEPPubKey []byte `json:"public_key"`
Err error `json:"err,omitempty"`
}
type blueprintsRequest struct{ Opts GetBlueprintsOption }
type blueprintsResponse struct {
Blueprints []blueprint.Blueprint `json:"blueprints"`
Err error `json:"err,omitempty"`
}
func (r blueprintsResponse) error() error { return r.Err }

View File

@@ -14,6 +14,7 @@ import (
"github.com/boltdb/bolt"
"github.com/micromdm/micromdm/blueprint"
"github.com/micromdm/micromdm/device"
)
@@ -25,14 +26,20 @@ type ListDevicesOption struct {
FilterUDID []string
}
type GetBlueprintsOption struct {
FilterName string
}
type Service interface {
ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error)
GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error)
GetBlueprints(ctx context.Context, opt GetBlueprintsOption) ([]blueprint.Blueprint, error)
}
type ListService struct {
Devices *device.DB
DB *bolt.DB // TODO: replace with reference to DEP token svc/pkg
Devices *device.DB
Blueprints *blueprint.DB
DB *bolt.DB // TODO: replace with reference to DEP token svc/pkg
}
func (svc *ListService) ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error) {
@@ -197,3 +204,19 @@ func SimpleSelfSignedRSAKeypair(cn string, days int) (key *rsa.PrivateKey, cert
return
}
func (svc *ListService) GetBlueprints(ctx context.Context, opt GetBlueprintsOption) ([]blueprint.Blueprint, error) {
if opt.FilterName != "" {
bp, err := svc.Blueprints.BlueprintByName(opt.FilterName)
if err != nil {
return nil, err
}
return []blueprint.Blueprint{*bp}, err
} else {
bps, err := svc.Blueprints.List()
if err != nil {
return nil, err
}
return bps, nil
}
}

View File

@@ -12,8 +12,9 @@ import (
)
type HTTPHandlers struct {
ListDevicesHandler http.Handler
GetDEPTokensHandler http.Handler
ListDevicesHandler http.Handler
GetDEPTokensHandler http.Handler
GetBlueprintsHandler http.Handler
}
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
@@ -29,6 +30,11 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
decodeGetDEPTokensRequest,
encodeResponse,
opts...),
GetBlueprintsHandler: httptransport.NewServer(
endpoints.GetBlueprintsEndpoint,
decodeGetBlueprintsRequest,
encodeResponse,
opts...),
}
return h
}
@@ -44,6 +50,17 @@ func decodeListDevicesRequest(ctx context.Context, r *http.Request) (interface{}
return req, nil
}
func decodeGetBlueprintsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var opts GetBlueprintsOption
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
return nil, err
}
req := blueprintsRequest{
Opts: opts,
}
return req, nil
}
func errorDecoder(r *http.Response) error {
var w errorWrapper
if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
@@ -72,7 +89,10 @@ func encodeResponse(ctx context.Context, w http.ResponseWriter, response interfa
}
func EncodeError(ctx context.Context, err error, w http.ResponseWriter) {
http.Error(w, err.Error(), http.StatusInternalServerError)
w.WriteHeader(http.StatusInternalServerError)
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
enc.Encode(errorWrapper{Error: err.Error()})
}
// EncodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that
@@ -103,3 +123,12 @@ func DecodeGetDEPTokensResponse(_ context.Context, r *http.Response) (interface{
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeGetBlueprintsResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp blueprintsResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

View File

@@ -192,7 +192,7 @@ func serve(args []string) error {
var listsvc list.Service
{
listsvc = &list.ListService{Devices: devDB, DB: sm.db}
listsvc = &list.ListService{Devices: devDB, DB: sm.db, Blueprints: bpDB}
}
var listDevicesEndpoint endpoint.Endpoint
{
@@ -200,8 +200,9 @@ func serve(args []string) error {
}
listEndpoints := list.Endpoints{
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: list.MakeGetDEPTokensEndpoint(listsvc),
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: list.MakeGetDEPTokensEndpoint(listsvc),
GetBlueprintsEndpoint: list.MakeGetBlueprintsEndpoint(listsvc),
}
var applysvc apply.Service
@@ -246,6 +247,7 @@ func serve(args []string) error {
r.Handle("/v1/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListDevicesHandler)).Methods("GET")
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPTokensHandler)).Methods("GET")
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DEPTokensHandler)).Methods("PUT")
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetBlueprintsHandler)).Methods("GET")
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.BlueprintHandler)).Methods("PUT")
}