mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-12 04:55:39 +08:00
implement mdmctl dep commands (#161)
adds subcommands for endpoints implemented in #160 mdmctl get dep-devices mdmctl get dep-account mdmctl get dep-profiles mdmctl apply dep-profiles Closes #154
This commit is contained in:
73
apply.go
73
apply.go
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/micromdm/dep"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func applyResource(args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("apply requires at least one resource name")
|
||||
}
|
||||
|
||||
sm := &config{}
|
||||
sm.setupBolt()
|
||||
client, err := sm.depClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var run func(dep.Client, []string) error
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "dep-profile":
|
||||
run = defineDEPProfile
|
||||
default:
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := run(client, args[1:]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func defineDEPProfile(client dep.Client, args []string) error {
|
||||
flagset := flag.NewFlagSet("dep-profile", flag.ExitOnError)
|
||||
var (
|
||||
flPath = flagset.String("f", "", "path to dep profile JSON")
|
||||
)
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *flPath == "" {
|
||||
return errors.New("must specify a path to a profile json")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(*flPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "reading DEP profile JSON file: %s", *flPath)
|
||||
}
|
||||
|
||||
var profile dep.Profile
|
||||
if err := json.Unmarshal(data, &profile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.DefineProfile(&profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("updated profile %s.\n", resp.ProfileUUID)
|
||||
return nil
|
||||
}
|
||||
@@ -52,6 +52,8 @@ func (cmd *applyCommand) Run(args []string) error {
|
||||
run = cmd.applyBlueprint
|
||||
case "dep-tokens":
|
||||
run = cmd.applyDEPTokens
|
||||
case "dep-profiles":
|
||||
run = cmd.applyDEPProfile
|
||||
case "profiles":
|
||||
run = cmd.applyProfile
|
||||
default:
|
||||
@@ -70,11 +72,15 @@ Valid resource types:
|
||||
* blueprints
|
||||
* profiles
|
||||
* dep-tokens
|
||||
* dep-profiles
|
||||
|
||||
Examples:
|
||||
# Get a list of devices
|
||||
# Apply a Blueprint.
|
||||
mdmctl apply blueprints -f /path/to/blueprint.json
|
||||
|
||||
# Apply a DEP Profile.
|
||||
mdmctl apply dep-profiles -f /path/to/dep-profile.json
|
||||
|
||||
`
|
||||
fmt.Println(applyUsage)
|
||||
return nil
|
||||
@@ -177,7 +183,8 @@ func (cmd *applyCommand) applyProfile(args []string) error {
|
||||
return err
|
||||
}
|
||||
if *flProfilePath == "" {
|
||||
return errors.New("must provide -f parameter")
|
||||
flagset.Usage()
|
||||
return errors.New("bad input: must provide -f parameter")
|
||||
}
|
||||
if _, err := os.Stat(*flProfilePath); os.IsNotExist(err) {
|
||||
return err
|
||||
|
||||
80
cmd/mdmctl/apply_dep_profile.go
Normal file
80
cmd/mdmctl/apply_dep_profile.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/micromdm/dep"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (cmd *applyCommand) applyDEPProfile(args []string) error {
|
||||
flagset := flag.NewFlagSet("dep-profiles", flag.ExitOnError)
|
||||
var (
|
||||
flProfilePath = flagset.String("f", "", "filename of DEP profile to apply")
|
||||
flTemplate = flagset.Bool("template", false, "print a JSON example of a DEP profile")
|
||||
)
|
||||
flagset.Usage = usageFor(flagset, "mdmctl apply dep-profiles [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *flTemplate {
|
||||
printDEPProfileTemplate()
|
||||
return nil
|
||||
}
|
||||
|
||||
if *flProfilePath == "" {
|
||||
flagset.Usage()
|
||||
return errors.New("bad input: must provide -f parameter")
|
||||
}
|
||||
|
||||
pf, err := os.Open(*flProfilePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "opening DEP profile file")
|
||||
}
|
||||
defer pf.Close()
|
||||
|
||||
var profile dep.Profile
|
||||
if err := json.NewDecoder(pf).Decode(&profile); err != nil {
|
||||
return errors.Wrap(err, "decode DEP Profile JSON")
|
||||
}
|
||||
|
||||
resp, err := cmd.applysvc.DefineDEPProfile(context.TODO(), &profile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "define dep profile")
|
||||
}
|
||||
|
||||
// TODO: it would be nice to encode back a profile that save the
|
||||
// UUID for future reference.
|
||||
fmt.Printf("Defined DEP Profile with UUID %s\n", resp.ProfileUUID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printDEPProfileTemplate() {
|
||||
|
||||
resp := `
|
||||
{
|
||||
"profile_name": "(Required) Human readable name",
|
||||
"url": "https://mymdm.example.org/mdm/enroll",
|
||||
"allow_pairing": true,
|
||||
"is_supervised": false,
|
||||
"is_multi_user": false,
|
||||
"is_mandatory": false,
|
||||
"await_device_configured": false,
|
||||
"is_mdm_removable": true,
|
||||
"support_phone_number": "(Optional) +1 408 555 1010",
|
||||
"support_email_address": "(Optional) support@example.com",
|
||||
"org_magic": "(Optional)",
|
||||
"anchor_certs": [],
|
||||
"supervising_host_certs": [],
|
||||
"skip_setup_items": ["AppleID", "Android"],
|
||||
"department": "(Optional) support@example.com",
|
||||
"devices": ["SERIAL1","SERIAL2"]
|
||||
}
|
||||
`
|
||||
fmt.Println(resp)
|
||||
}
|
||||
@@ -51,6 +51,12 @@ func (cmd *getCommand) Run(args []string) error {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "devices":
|
||||
run = cmd.getDevices
|
||||
case "dep-devices":
|
||||
run = cmd.getDEPDevices
|
||||
case "dep-account":
|
||||
run = cmd.getDEPAccount
|
||||
case "dep-profiles":
|
||||
run = cmd.getDEPProfiles
|
||||
case "dep-tokens":
|
||||
run = cmd.getDepTokens
|
||||
case "blueprints":
|
||||
@@ -74,6 +80,9 @@ Valid resource types:
|
||||
* devices
|
||||
* blueprints
|
||||
* dep-tokens
|
||||
* dep-devices
|
||||
* dep-account
|
||||
* dep-profiles
|
||||
* profiles
|
||||
|
||||
Examples:
|
||||
|
||||
38
cmd/mdmctl/get_dep_account.go
Normal file
38
cmd/mdmctl/get_dep_account.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type depaccountTableOutput struct{ w *tabwriter.Writer }
|
||||
|
||||
func (out *depaccountTableOutput) BasicHeader() {
|
||||
fmt.Fprintf(out.w, "OrgName\tOrgPhone\tOrgEmail\tServerName\n")
|
||||
}
|
||||
|
||||
func (out *depaccountTableOutput) BasicFooter() {
|
||||
out.w.Flush()
|
||||
}
|
||||
|
||||
func (cmd *getCommand) getDEPAccount(args []string) error {
|
||||
flagset := flag.NewFlagSet("dep-account", flag.ExitOnError)
|
||||
flagset.Usage = usageFor(flagset, "mdmctl get dep-account [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stderr, 0, 4, 2, ' ', 0)
|
||||
out := &depaccountTableOutput{w}
|
||||
out.BasicHeader()
|
||||
defer out.BasicFooter()
|
||||
ctx := context.Background()
|
||||
resp, err := cmd.list.GetDEPAccountInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(out.w, "%s\t%s\t%s\t%s\n", resp.OrgName, resp.OrgPhone, resp.OrgEmail, resp.ServerName)
|
||||
return nil
|
||||
}
|
||||
48
cmd/mdmctl/get_dep_devices.go
Normal file
48
cmd/mdmctl/get_dep_devices.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type depDevicesTableOutput struct{ w *tabwriter.Writer }
|
||||
|
||||
func (out *depDevicesTableOutput) BasicHeader() {
|
||||
fmt.Fprintf(out.w, "SerialNumber\tModel\tProfileStatus\tProfileUUID\n")
|
||||
}
|
||||
|
||||
func (out *depDevicesTableOutput) BasicFooter() {
|
||||
out.w.Flush()
|
||||
}
|
||||
|
||||
func (cmd *getCommand) getDEPDevices(args []string) error {
|
||||
flagset := flag.NewFlagSet("dep-devices", flag.ExitOnError)
|
||||
flSerials := flagset.String("serials", "", "comma separated list of device serials")
|
||||
flagset.Usage = usageFor(flagset, "mdmctl get dep-devices [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if *flSerials == "" {
|
||||
flagset.Usage()
|
||||
return errors.New("bad input: must provide a comma separated list of DEP serials")
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stderr, 0, 4, 2, ' ', 0)
|
||||
out := &depDevicesTableOutput{w}
|
||||
out.BasicHeader()
|
||||
defer out.BasicFooter()
|
||||
ctx := context.Background()
|
||||
serials := strings.Split(*flSerials, ",")
|
||||
resp, err := cmd.list.GetDEPDevice(ctx, serials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range resp.Devices {
|
||||
fmt.Fprintf(out.w, "%s\t%s\t%s\t%s\n", d.SerialNumber, d.Model, d.ProfileStatus, d.ProfileUUID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
47
cmd/mdmctl/get_dep_profiles.go
Normal file
47
cmd/mdmctl/get_dep_profiles.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type depProfilesTableOutput struct{ w *tabwriter.Writer }
|
||||
|
||||
func (out *depProfilesTableOutput) BasicHeader() {
|
||||
fmt.Fprintf(out.w, "Name\tMandatory\tRemovable\tAwaitConfigured\tSkippedItems\n")
|
||||
}
|
||||
|
||||
func (out *depProfilesTableOutput) BasicFooter() {
|
||||
out.w.Flush()
|
||||
}
|
||||
|
||||
func (cmd *getCommand) getDEPProfiles(args []string) error {
|
||||
flagset := flag.NewFlagSet("dep-profiles", flag.ExitOnError)
|
||||
flUUID := flagset.String("uuid", "", "DEP Profile UUID")
|
||||
flagset.Usage = usageFor(flagset, "mdmctl get dep-profiles [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stderr, 0, 4, 2, ' ', 0)
|
||||
out := &depProfilesTableOutput{w}
|
||||
out.BasicHeader()
|
||||
defer out.BasicFooter()
|
||||
ctx := context.Background()
|
||||
resp, err := cmd.list.GetDEPProfile(ctx, *flUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(out.w, "%s\t%v\t%v\t%v\t%s\n",
|
||||
resp.ProfileName,
|
||||
resp.IsMandatory,
|
||||
resp.IsMDMRemovable,
|
||||
resp.AwaitDeviceConfigured,
|
||||
strings.Join(resp.SkipSetupItems, ","),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
73
device.go
73
device.go
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/micromdm/micromdm/device"
|
||||
)
|
||||
|
||||
// TODO this is a temporary command to get some stuff working
|
||||
// we need to remove this and add more robust/general "list/describe" commands
|
||||
func dev(args []string) error {
|
||||
flagset := flag.NewFlagSet("devinfo", flag.ExitOnError)
|
||||
var (
|
||||
flList = flagset.Bool("list", false, "list all bucket keys")
|
||||
flDescribe = flagset.String("describe", "", "describe a serial")
|
||||
)
|
||||
flagset.Usage = usageFor(flagset, "micromdm serve [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sm := &config{}
|
||||
sm.setupBolt()
|
||||
db, err := device.NewDB(sm.db, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *flDescribe != "" {
|
||||
err := describeDevice(db, *flDescribe)
|
||||
return err
|
||||
}
|
||||
|
||||
if *flList {
|
||||
err := listDevices(db)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func describeDevice(db *device.DB, serial string) error {
|
||||
dev, err := db.DeviceBySerial(serial)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" udid=%s\n", dev.UDID)
|
||||
fmt.Printf(" serial=%s\n", dev.SerialNumber)
|
||||
fmt.Printf(" prduct_name=%s\n", dev.ProductName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func listDevices(db *device.DB) error {
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(device.DeviceBucket))
|
||||
if b == nil {
|
||||
return errors.New("no device bucket found")
|
||||
}
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
164
get.go
164
get.go
@@ -1,164 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/micromdm/dep"
|
||||
)
|
||||
|
||||
func getResource(args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("get requires at least one resource name")
|
||||
}
|
||||
|
||||
var run func([]string) error
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "dep":
|
||||
run = getDep
|
||||
default:
|
||||
return errors.New("invalid dep resource name")
|
||||
}
|
||||
|
||||
if err := run(args[1:]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDep(args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("get dep requires at least one resource name")
|
||||
}
|
||||
sm := &config{}
|
||||
sm.setupBolt()
|
||||
client, err := sm.depClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var run func(dep.Client, []string) error
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "account-info":
|
||||
acc, err := client.Account()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(acc)
|
||||
return err
|
||||
case "device":
|
||||
run = getDepDevice
|
||||
case "profile":
|
||||
run = getDepProfile
|
||||
case "profile-template":
|
||||
run = getDepProfileTpl
|
||||
default:
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := run(client, args[1:]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDepProfile(client dep.Client, args []string) error {
|
||||
flagset := flag.NewFlagSet("profile", flag.ExitOnError)
|
||||
var (
|
||||
flUUID = flagset.String("uuid", "", "profile uuid")
|
||||
)
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profile, err := client.FetchProfile(*flUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(profile)
|
||||
return err
|
||||
}
|
||||
|
||||
func getDepDevice(client dep.Client, args []string) error {
|
||||
flagset := flag.NewFlagSet("device", flag.ExitOnError)
|
||||
var (
|
||||
flSerial = flagset.String("serial", "", "device serial number")
|
||||
)
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.DeviceDetails([]string{*flSerial})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dev, ok := resp.Devices[*flSerial]; ok && dev.SerialNumber != "" {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(dev)
|
||||
return err
|
||||
} else {
|
||||
return errors.New("no device information")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDepProfileTpl(client dep.Client, args []string) error {
|
||||
/* omitempty flags on the Profile struct are preventing the (Apple-
|
||||
defined) false defaults from showing up in the example.
|
||||
|
||||
p := dep.Profile{
|
||||
ProfileName: "(Required) Human readable name",
|
||||
URL: "https://mymdm.example.org",
|
||||
AllowPairing: true,
|
||||
IsSupervised: false,
|
||||
IsMultiUser: false,
|
||||
IsMandatory: false,
|
||||
AwaitDeviceConfigured: false,
|
||||
IsMDMRemovable: true,
|
||||
SupportPhoneNumber: "(Optional) +1 408 555 1010",
|
||||
SupportEmailAddress: "(Optional) support@example.com",
|
||||
OrgMagic: "(Optional)",
|
||||
AnchorCerts: []string{},
|
||||
SupervisingHostCerts: []string{},
|
||||
SkipSetupItems: []string{"AppleID", "Android"},
|
||||
Department: "(Optional) support@example.com",
|
||||
Devices: []string{},
|
||||
}
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
err := enc.Encode(&p)
|
||||
*/
|
||||
|
||||
resp := `{
|
||||
"profile_name": "(Required) Human readable name",
|
||||
"url": "https://mymdm.example.org",
|
||||
"allow_pairing": true,
|
||||
"is_supervised": false,
|
||||
"is_multi_user": false,
|
||||
"is_mandatory": false,
|
||||
"await_device_configured": false,
|
||||
"is_mdm_removable": true,
|
||||
"support_phone_number": "(Optional) +1 408 555 1010",
|
||||
"support_email_address": "(Optional) support@example.com",
|
||||
"org_magic": "(Optional)",
|
||||
"anchor_certs": [],
|
||||
"supervising_host_certs": [],
|
||||
"skip_setup_items": ["AppleID", "Android"],
|
||||
"deparment": "(Optional) support@example.com",
|
||||
"devices": []
|
||||
}`
|
||||
fmt.Println(resp)
|
||||
return nil
|
||||
}
|
||||
9
main.go
9
main.go
@@ -28,12 +28,6 @@ func main() {
|
||||
return
|
||||
case "serve":
|
||||
run = serve
|
||||
case "get":
|
||||
run = getResource
|
||||
case "apply":
|
||||
run = applyResource
|
||||
case "dev":
|
||||
run = dev
|
||||
default:
|
||||
usage()
|
||||
os.Exit(1)
|
||||
@@ -50,9 +44,6 @@ func usage() error {
|
||||
|
||||
Available Commands:
|
||||
serve
|
||||
dev
|
||||
get
|
||||
apply
|
||||
version
|
||||
|
||||
Use micromdm <command> -h for additional usage of each command.
|
||||
|
||||
Reference in New Issue
Block a user