diff --git a/apply.go b/apply.go deleted file mode 100644 index 8054f2a7..00000000 --- a/apply.go +++ /dev/null @@ -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 -} diff --git a/cmd/mdmctl/apply.go b/cmd/mdmctl/apply.go index da69e53c..a9670426 100644 --- a/cmd/mdmctl/apply.go +++ b/cmd/mdmctl/apply.go @@ -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 diff --git a/cmd/mdmctl/apply_dep_profile.go b/cmd/mdmctl/apply_dep_profile.go new file mode 100644 index 00000000..bc5e9001 --- /dev/null +++ b/cmd/mdmctl/apply_dep_profile.go @@ -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) +} diff --git a/cmd/mdmctl/get.go b/cmd/mdmctl/get.go index d9ddb070..7cd33966 100644 --- a/cmd/mdmctl/get.go +++ b/cmd/mdmctl/get.go @@ -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: diff --git a/cmd/mdmctl/get_dep_account.go b/cmd/mdmctl/get_dep_account.go new file mode 100644 index 00000000..5fa8398c --- /dev/null +++ b/cmd/mdmctl/get_dep_account.go @@ -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 +} diff --git a/cmd/mdmctl/get_dep_devices.go b/cmd/mdmctl/get_dep_devices.go new file mode 100644 index 00000000..4ec42260 --- /dev/null +++ b/cmd/mdmctl/get_dep_devices.go @@ -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 +} diff --git a/cmd/mdmctl/get_dep_profiles.go b/cmd/mdmctl/get_dep_profiles.go new file mode 100644 index 00000000..24b2220e --- /dev/null +++ b/cmd/mdmctl/get_dep_profiles.go @@ -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 +} diff --git a/device.go b/device.go deleted file mode 100644 index 050b3632..00000000 --- a/device.go +++ /dev/null @@ -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 -} diff --git a/get.go b/get.go deleted file mode 100644 index 0174676b..00000000 --- a/get.go +++ /dev/null @@ -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 -} diff --git a/main.go b/main.go index a38cf9a0..a0ac53cd 100644 --- a/main.go +++ b/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 -h for additional usage of each command.