mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-14 17:41:43 +08:00
Blueprints apply and list/get (#145)
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -67,3 +67,4 @@ type depTokensResponse struct {
|
||||
}
|
||||
|
||||
func (r depTokensResponse) error() error { return r.Err }
|
||||
func (r blueprintResponse) error() error { return r.Err }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
8
serve.go
8
serve.go
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user