add list devices api (#114)

This commit is contained in:
Victor Vrantchan
2017-03-24 22:40:14 -04:00
committed by GitHub
parent 511c58b9e8
commit 5c66bfdbcf
8 changed files with 357 additions and 1 deletions

59
cmd/mdmctl/list.go Normal file
View File

@@ -0,0 +1,59 @@
package main
import (
"context"
"errors"
"fmt"
"os"
"text/tabwriter"
"github.com/go-kit/kit/log"
"github.com/micromdm/micromdm/core/list"
)
type tableOutput struct{ w *tabwriter.Writer }
func (out *tableOutput) BasicHeader() {
fmt.Fprintf(out.w, "UDID\tSerialNumber\tEnrollmentStatus\tLastSeen\n")
}
func (out *tableOutput) BasicFooter() {
out.w.Flush()
}
func (out *tableOutput) BasicLine(line ...string) {
for _, l := range line {
fmt.Fprintf(out.w, l+"\t")
}
fmt.Fprintf(out.w, "\n")
}
func listDevices(args []string) error {
if len(args) == 0 || args[0] != "devices" {
return errors.New("must specify resource name as an argument (ex: devices)")
}
w := tabwriter.NewWriter(os.Stderr, 0, 4, 2, ' ', 0)
out := &tableOutput{w}
out.BasicHeader()
defer out.BasicFooter()
logger := log.NewLogfmtLogger(os.Stderr)
// TODO needs some config for authentication and server url client side.
instance := os.Getenv("MICROMDM_SERVER_URL")
if instance == "" {
return errors.New("MICROMDM_SERVER_URL not set")
}
svc, err := list.NewClient(instance, logger)
if err != nil {
return err
}
ctx := context.Background()
devices, err := svc.ListDevices(ctx, list.ListDevicesOption{})
if err != nil {
return err
}
for _, d := range devices {
fmt.Fprintf(out.w, "%s\t%s\t%v\t%s\n", d.UDID, d.SerialNumber, d.EnrollmentStatus, d.LastSeen)
}
return nil
}

63
cmd/mdmctl/mdmdctl.go Normal file
View File

@@ -0,0 +1,63 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/micromdm/micromdm/version"
)
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(1)
}
var run func([]string) error
switch strings.ToLower(os.Args[1]) {
case "version", "-version":
version.Print()
return
case "list":
run = listDevices
default:
usage()
os.Exit(1)
}
if err := run(os.Args[2:]); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
func usage() error {
helpText := `USAGE: mdmctl <COMMAND>
Available Commands:
list
version
Use micromdm <command> -h for additional usage of each command.
Example: micromdm serve -h
`
fmt.Println(helpText)
return nil
}
func usageFor(fs *flag.FlagSet, short string) func() {
return func() {
fmt.Fprintf(os.Stderr, "USAGE\n")
fmt.Fprintf(os.Stderr, " %s\n", short)
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, "FLAGS\n")
w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
fs.VisitAll(func(f *flag.Flag) {
fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage)
})
w.Flush()
fmt.Fprintf(os.Stderr, "\n")
}
}

36
core/list/client.go Normal file
View File

@@ -0,0 +1,36 @@
package list
import (
"net/url"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
)
func NewClient(instance string, logger log.Logger) (Service, error) {
u, err := url.Parse(instance)
if err != nil {
return nil, err
}
var listDevicesEndpoint endpoint.Endpoint
{
listDevicesEndpoint = httptransport.NewClient(
"GET",
copyURL(u, "/v1/devices"),
EncodeHTTPGenericRequest,
DecodeDevicesResponse,
).Endpoint()
}
return Endpoints{
ListDevicesEndpoint: listDevicesEndpoint,
}, nil
}
func copyURL(base *url.URL, path string) *url.URL {
next := *base
next.Path = path
return &next
}

45
core/list/endpoint.go Normal file
View File

@@ -0,0 +1,45 @@
package list
import (
"context"
"time"
"github.com/go-kit/kit/endpoint"
)
type Endpoints struct {
ListDevicesEndpoint endpoint.Endpoint
}
func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]DeviceDTO, error) {
request := devicesRequest{opts}
response, err := e.ListDevicesEndpoint(ctx, request.Opts)
if err != nil {
return nil, err
}
return response.(devicesResponse).Devices, response.(devicesResponse).Err
}
func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(devicesRequest)
dto, err := svc.ListDevices(ctx, req.Opts)
return devicesResponse{
Devices: dto,
Err: err,
}, nil
}
}
type DeviceDTO struct {
SerialNumber string `json:"serial_number"`
UDID string `json:"udid"`
EnrollmentStatus bool `json:"enrollment_status"`
LastSeen time.Time `json:"last_seen"`
}
type devicesRequest struct{ Opts ListDevicesOption }
type devicesResponse struct {
Devices []DeviceDTO `json:"devices"`
Err error `json:"err,omitempty"`
}

37
core/list/service.go Normal file
View File

@@ -0,0 +1,37 @@
package list
import (
"context"
"github.com/micromdm/micromdm/device"
)
type ListDevicesOption struct {
Page int
PerPage int
FilterSerial []string
FilterUDID []string
}
type Service interface {
ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error)
}
type ListService struct {
Devices *device.DB
}
func (svc *ListService) ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error) {
devices, err := svc.Devices.List()
dto := []DeviceDTO{}
for _, d := range devices {
dto = append(dto, DeviceDTO{
SerialNumber: d.SerialNumber,
UDID: d.UDID,
EnrollmentStatus: d.Enrolled,
LastSeen: d.LastCheckin,
})
}
return dto, err
}

View File

@@ -0,0 +1,81 @@
package list
import (
"bytes"
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
httptransport "github.com/go-kit/kit/transport/http"
)
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) http.Handler {
var h http.Handler
h = httptransport.NewServer(
endpoints.ListDevicesEndpoint,
decodeListDevicesRequest,
encodeResponse,
opts...,
)
return h
}
func decodeListDevicesRequest(ctx context.Context, r *http.Request) (interface{}, error) {
req := devicesRequest{
Opts: ListDevicesOption{},
}
return req, nil
}
func errorDecoder(r *http.Response) error {
var w errorWrapper
if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
return err
}
return errors.New(w.Error)
}
type errorWrapper struct {
Error string `json:"error"`
}
type errorer interface {
error() error
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
EncodeError(ctx, e.error(), w)
return nil
}
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(response)
}
func EncodeError(ctx context.Context, err error, w http.ResponseWriter) {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
// EncodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that
// JSON-encodes any request to the request body. Primarily useful in a client.
func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interface{}) error {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(request); err != nil {
return err
}
r.Body = ioutil.NopCloser(&buf)
return nil
}
func DecodeDevicesResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp devicesResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

View File

@@ -47,6 +47,24 @@ func NewDB(db *bolt.DB, sub pubsub.Subscriber) (*DB, error) {
return datastore, nil
}
func (db *DB) List() ([]Device, error) {
// TODO add filter/limit with ForEach
var devices []Device
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(DeviceBucket))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var dev Device
if err := UnmarshalDevice(v, &dev); err != nil {
return err
}
devices = append(devices, dev)
}
return nil
})
return devices, err
}
func (db *DB) Save(dev *Device) error {
tx, err := db.DB.Begin(true)
if err != nil {

View File

@@ -43,6 +43,7 @@ import (
"github.com/micromdm/micromdm/checkin"
"github.com/micromdm/micromdm/command"
"github.com/micromdm/micromdm/connect"
"github.com/micromdm/micromdm/core/list"
"github.com/micromdm/micromdm/depsync"
"github.com/micromdm/micromdm/device"
"github.com/micromdm/micromdm/enroll"
@@ -136,7 +137,7 @@ func serve(args []string) error {
stdlog.Fatal(err)
}
_, err := device.NewDB(sm.db, sm.pubclient)
devDB, err := device.NewDB(sm.db, sm.pubclient)
if err != nil {
stdlog.Fatal(err)
}
@@ -180,6 +181,21 @@ func serve(args []string) error {
ConnectEndpoint: connectEndpoint,
}
var listsvc list.Service
{
listsvc = &list.ListService{Devices: devDB}
}
var listDevicesEndpoint endpoint.Endpoint
{
listDevicesEndpoint = list.MakeListDevicesEndpoint(listsvc)
}
listEndpoints := list.Endpoints{
ListDevicesEndpoint: listDevicesEndpoint,
}
listAPIHandlers := list.MakeHTTPHandlers(ctx, listEndpoints, connectOpts...)
connectHandlers := connect.MakeHTTPHandlers(ctx, connectEndpoints, connectOpts...)
pushHandlers := nanopush.MakeHTTPHandlers(ctx, pushEndpoints, checkinOpts...)
@@ -192,6 +208,7 @@ func serve(args []string) error {
r.Handle("/scep", scepHandler)
r.Handle("/push/{udid}", pushHandlers.PushHandler)
r.Handle("/v1/commands", commandHandlers.NewCommandHandler).Methods("POST")
r.Handle("/v1/devices", listAPIHandlers).Methods("GET")
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, homePage)
})