mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-08 18:55:34 +08:00
clean up command service
This commit is contained in:
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/go-kit/kit/log"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/groob/plist"
|
||||
"github.com/micromdm/mdm"
|
||||
)
|
||||
@@ -18,7 +18,7 @@ var (
|
||||
ErrNoKey = errors.New("There is no such key in redis.")
|
||||
)
|
||||
|
||||
// Datastore manages MDM Payloads in redis
|
||||
// Datastore provides methods for saving and retrieving MDM commands
|
||||
type Datastore interface {
|
||||
// Saves the payload in redis
|
||||
// SET CommandUUID plistData
|
||||
@@ -30,68 +30,20 @@ type Datastore interface {
|
||||
DeleteCommand(deviceUDID, commandUUID string) (int, error)
|
||||
}
|
||||
|
||||
type redisDB struct {
|
||||
pool *redis.Pool
|
||||
}
|
||||
|
||||
// NewDB creates a new databases connection
|
||||
func NewDB(driver, conn string, options ...func(*config) error) Datastore {
|
||||
conf := &config{}
|
||||
defaultLogger := log.NewLogfmtLogger(os.Stderr)
|
||||
for _, option := range options {
|
||||
if err := option(conf); err != nil {
|
||||
defaultLogger.Log("err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
//NewDB creates a Datastore
|
||||
func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) {
|
||||
var ds Datastore
|
||||
switch driver {
|
||||
case "redis":
|
||||
return redisDB{pool: redisPool(conn, conf.logger)}
|
||||
ds = redisDB{pool: redisPool(conn, logger)}
|
||||
return ds, nil
|
||||
default:
|
||||
conf.logger.Log("err", "unknown driver")
|
||||
os.Exit(1)
|
||||
return nil
|
||||
return nil, errors.New("unknown driver")
|
||||
}
|
||||
}
|
||||
|
||||
func redisPool(conn string, logger log.Logger) *redis.Pool {
|
||||
pool := &redis.Pool{
|
||||
MaxIdle: 3,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
Dial: func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
checkRedisConn(pool, logger)
|
||||
return pool
|
||||
}
|
||||
|
||||
func checkRedisConn(pool *redis.Pool, logger log.Logger) {
|
||||
conn := pool.Get()
|
||||
defer conn.Close()
|
||||
|
||||
var dbError error
|
||||
maxAttempts := 20
|
||||
for attempts := 1; attempts <= maxAttempts; attempts++ {
|
||||
_, dbError = conn.Do("PING")
|
||||
if dbError == nil {
|
||||
break
|
||||
}
|
||||
logger.Log("msg", fmt.Sprintf("could not connect to redis: %v", dbError))
|
||||
time.Sleep(time.Duration(attempts) * time.Second)
|
||||
}
|
||||
if dbError != nil {
|
||||
logger.Log("err", dbError)
|
||||
os.Exit(1)
|
||||
}
|
||||
type redisDB struct {
|
||||
pool *redis.Pool
|
||||
}
|
||||
|
||||
func (rds redisDB) SavePayload(payload *mdm.Payload) error {
|
||||
@@ -171,3 +123,43 @@ func (rds redisDB) DeleteCommand(deviceUDID, commandUUID string) (int, error) {
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func redisPool(conn string, logger kitlog.Logger) *redis.Pool {
|
||||
pool := &redis.Pool{
|
||||
MaxIdle: 3,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
Dial: func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
checkRedisConn(pool, logger)
|
||||
return pool
|
||||
}
|
||||
|
||||
func checkRedisConn(pool *redis.Pool, logger kitlog.Logger) {
|
||||
conn := pool.Get()
|
||||
defer conn.Close()
|
||||
|
||||
var dbError error
|
||||
maxAttempts := 20
|
||||
for attempts := 1; attempts <= maxAttempts; attempts++ {
|
||||
_, dbError = conn.Do("PING")
|
||||
if dbError == nil {
|
||||
break
|
||||
}
|
||||
logger.Log("msg", fmt.Sprintf("could not connect to redis: %v", dbError))
|
||||
time.Sleep(time.Duration(attempts) * time.Second)
|
||||
}
|
||||
if dbError != nil {
|
||||
logger.Log("err", dbError)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
100
command/endpoint.go
Normal file
100
command/endpoint.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/micromdm/mdm"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyRequest is returned if the request body is empty
|
||||
ErrEmptyRequest = errors.New("request must contain UDID of the device")
|
||||
errBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
|
||||
)
|
||||
|
||||
// newCommandRequest represents an HTTP Request for a new MDM Command
|
||||
type newCommandRequest struct {
|
||||
*mdm.CommandRequest
|
||||
}
|
||||
|
||||
// newCommandResponse is a command reponse
|
||||
type newCommandResponse struct {
|
||||
*mdm.Payload
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r newCommandResponse) error() error { return r.Err }
|
||||
|
||||
func makeNewCommandEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(newCommandRequest)
|
||||
if req.UDID == "" || req.RequestType == "" {
|
||||
return newCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
payload, err := svc.NewCommand(req.CommandRequest)
|
||||
if err != nil {
|
||||
return newCommandResponse{Err: err}, nil
|
||||
}
|
||||
return newCommandResponse{Payload: payload}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// NextCommandRequest is a request to return the next command in a device queue
|
||||
type nextCommandRequest struct {
|
||||
UDID string
|
||||
}
|
||||
|
||||
// NextCommandResponse is a response for the next command
|
||||
type nextCommandResponse struct {
|
||||
Payload []byte `json:"command_payload"`
|
||||
Total int `json:"total_payloads"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func makeNextCommandEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(nextCommandRequest)
|
||||
if req.UDID == "" {
|
||||
return nextCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
payload, total, err := svc.NextCommand(req.UDID)
|
||||
if err != nil {
|
||||
return nextCommandResponse{Err: err}, nil
|
||||
}
|
||||
return nextCommandResponse{Payload: payload, Total: total}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// deleteCommandRequest is a request to delete a command
|
||||
type deleteCommandRequest struct {
|
||||
// device UDID
|
||||
UDID string
|
||||
// command UUID
|
||||
UUID string
|
||||
}
|
||||
|
||||
// deleteCommandResponse is a response for a delete request
|
||||
type deleteCommandResponse struct {
|
||||
Total int `json:"remaining_payloads"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func makeDeleteCommandEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(deleteCommandRequest)
|
||||
if req.UDID == "" {
|
||||
return deleteCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
if req.UUID == "" {
|
||||
return deleteCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
total, err := svc.DeleteCommand(req.UDID, req.UUID)
|
||||
if err != nil {
|
||||
return deleteCommandResponse{Err: err}, nil
|
||||
}
|
||||
return deleteCommandResponse{Total: total}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,26 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
import "github.com/micromdm/mdm"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/micromdm/mdm"
|
||||
)
|
||||
|
||||
// MDMCommandService allows creating and deleting MDM Command Payloads
|
||||
type MDMCommandService interface {
|
||||
// Service defines methods for managing MDM commands
|
||||
type Service interface {
|
||||
NewCommand(*mdm.CommandRequest) (*mdm.Payload, error)
|
||||
NextCommand(udid string) ([]byte, int, error)
|
||||
DeleteCommand(deviceUDID, commandUUID string) (int, error)
|
||||
}
|
||||
|
||||
type mdmCommandService struct {
|
||||
// a redis datastore
|
||||
// NewService returns a new command service
|
||||
func NewService(ds Datastore) Service {
|
||||
return &service{
|
||||
db: ds,
|
||||
}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
db Datastore
|
||||
}
|
||||
|
||||
type config struct {
|
||||
logger log.Logger
|
||||
db Datastore
|
||||
}
|
||||
|
||||
// NewCommandService creates a new MDM Command Service
|
||||
func NewCommandService(options ...func(*config) error) MDMCommandService {
|
||||
conf := &config{}
|
||||
defaultLogger := log.NewLogfmtLogger(os.Stderr)
|
||||
for _, option := range options {
|
||||
if err := option(conf); err != nil {
|
||||
defaultLogger.Log("err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
var svc MDMCommandService
|
||||
svc = mdmCommandService{db: conf.db}
|
||||
return svc
|
||||
}
|
||||
|
||||
// Logger adds a logger to the service
|
||||
func Logger(logger log.Logger) func(*config) error {
|
||||
return func(c *config) error {
|
||||
c.logger = logger
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DB adds a db connection to the service
|
||||
func DB(db Datastore) func(*config) error {
|
||||
return func(c *config) error {
|
||||
c.db = db
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceHandler returns an http handler for the command service
|
||||
func ServiceHandler(ctx context.Context, svc MDMCommandService) http.Handler {
|
||||
commonOptions := []httptransport.ServerOption{
|
||||
httptransport.ServerErrorEncoder(encodeError),
|
||||
}
|
||||
newCommandEndpoint := makeNewCommandEndpoint(svc)
|
||||
newCommandHandler := httptransport.NewServer(
|
||||
ctx,
|
||||
newCommandEndpoint,
|
||||
decodeNewCommandRequest,
|
||||
encodeResponse,
|
||||
commonOptions...,
|
||||
)
|
||||
nextCommandEndpoint := makeNextCommandEndpoint(svc)
|
||||
nextCommandHandler := httptransport.NewServer(
|
||||
ctx,
|
||||
nextCommandEndpoint,
|
||||
decodeNextCommandRequest,
|
||||
encodeResponse,
|
||||
commonOptions...,
|
||||
)
|
||||
deleteCommandEndpoint := makeDeleteCommandEndpoint(svc)
|
||||
deleteCommandHandler := httptransport.NewServer(
|
||||
ctx,
|
||||
deleteCommandEndpoint,
|
||||
decodeDeleteCommandRequest,
|
||||
encodeResponse,
|
||||
commonOptions...,
|
||||
)
|
||||
r := mux.NewRouter()
|
||||
r.Methods("POST").Path("/mdm/commands").Handler(newCommandHandler)
|
||||
r.Methods("GET").Path("/mdm/commands/{udid}/next").Handler(nextCommandHandler)
|
||||
r.Methods("DELETE").Path("/mdm/commands/{udid}/{uuid}").Handler(deleteCommandHandler)
|
||||
return r
|
||||
}
|
||||
|
||||
func (svc mdmCommandService) NewCommand(request *mdm.CommandRequest) (*mdm.Payload, error) {
|
||||
func (svc service) NewCommand(request *mdm.CommandRequest) (*mdm.Payload, error) {
|
||||
// create a payload
|
||||
payload, err := mdm.NewPayload(request)
|
||||
if err != nil {
|
||||
@@ -118,11 +41,11 @@ func (svc mdmCommandService) NewCommand(request *mdm.CommandRequest) (*mdm.Paylo
|
||||
}
|
||||
|
||||
// NextCommand returns an MDM Payload from a list of queued payloads
|
||||
func (svc mdmCommandService) NextCommand(udid string) ([]byte, int, error) {
|
||||
func (svc service) NextCommand(udid string) ([]byte, int, error) {
|
||||
return svc.db.NextCommand(udid)
|
||||
}
|
||||
|
||||
// DeleteCommand returns an MDM Payload from a list of queued payloads
|
||||
func (svc mdmCommandService) DeleteCommand(deviceUDID, commandUUID string) (int, error) {
|
||||
func (svc service) DeleteCommand(deviceUDID, commandUUID string) (int, error) {
|
||||
return svc.db.DeleteCommand(deviceUDID, commandUUID)
|
||||
}
|
||||
|
||||
@@ -1,133 +1,71 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/micromdm/mdm"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyRequest is returned if the request body is empty
|
||||
ErrEmptyRequest = errors.New("request must contain UDID of the device")
|
||||
errBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
|
||||
)
|
||||
// ServiceHandler returns an HTTP Handler for the command service
|
||||
func ServiceHandler(ctx context.Context, svc Service, logger kitlog.Logger) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorLogger(logger),
|
||||
kithttp.ServerErrorEncoder(encodeError),
|
||||
}
|
||||
|
||||
// NewCommandRequest represents an HTTP Request for a new MDM Command
|
||||
type NewCommandRequest struct {
|
||||
*mdm.CommandRequest
|
||||
newCommandHandler := kithttp.NewServer(
|
||||
ctx,
|
||||
makeNewCommandEndpoint(svc),
|
||||
decodeNewCommandRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
)
|
||||
nextCommandHandler := kithttp.NewServer(
|
||||
ctx,
|
||||
makeNextCommandEndpoint(svc),
|
||||
decodeNextCommandRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
)
|
||||
deleteCommandHandler := kithttp.NewServer(
|
||||
ctx,
|
||||
makeDeleteCommandEndpoint(svc),
|
||||
decodeDeleteCommandRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
)
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
||||
r.Handle("/mdm/commands", newCommandHandler).Methods("POST")
|
||||
r.Handle("/mdm/commands/{udid}/next", nextCommandHandler).Methods("GET")
|
||||
r.Handle("/mdm/commands/{udid}/{uuid}", deleteCommandHandler).Methods("DELETE")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func decodeNewCommandRequest(r *http.Request) (interface{}, error) {
|
||||
var request NewCommandRequest
|
||||
func decodeNewCommandRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
var request newCommandRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&request.CommandRequest)
|
||||
return request, err
|
||||
}
|
||||
|
||||
// NewCommandResponse is a command reponse
|
||||
type NewCommandResponse struct {
|
||||
*mdm.Payload
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r NewCommandResponse) error() error { return r.Err }
|
||||
|
||||
// errorer is implemented by all concrete response types. It allows us to
|
||||
// change the HTTP response code without needing to trigger an endpoint
|
||||
// (transport-level) error. For more information, read the big comment in
|
||||
// endpoint.go.
|
||||
type errorer interface {
|
||||
error() error
|
||||
}
|
||||
|
||||
// NextCommandRequest is a request to return the next command in a device queue
|
||||
type NextCommandRequest struct {
|
||||
UDID string
|
||||
}
|
||||
|
||||
func decodeNextCommandRequest(r *http.Request) (interface{}, error) {
|
||||
func decodeNextCommandRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
vars := mux.Vars(r)
|
||||
udid, ok := vars["udid"]
|
||||
if !ok {
|
||||
return nil, errBadRouting
|
||||
}
|
||||
var request NextCommandRequest
|
||||
var request nextCommandRequest
|
||||
request.UDID = udid
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// EncodeNextCommandRequest encodes a request for the NextCommand endpoint
|
||||
func EncodeNextCommandRequest(r *http.Request, request interface{}) error {
|
||||
req := request.(NextCommandRequest)
|
||||
path := r.URL.Path
|
||||
r.URL.Path = fmt.Sprintf("%v/%v/next", path, req.UDID)
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(request); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(&buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextCommandResponse is a response for the next command
|
||||
type NextCommandResponse struct {
|
||||
Payload []byte `json:"command_payload"`
|
||||
Total int `json:"total_payloads"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// DecodeNextCommandResponse decodes the response from the provided HTTP response,
|
||||
// simply by JSON decoding from the response body. It's designed to be used in
|
||||
// transport/http.Client.
|
||||
// first decode into map[string]interface{} and check for error in the response
|
||||
func DecodeNextCommandResponse(resp *http.Response) (interface{}, error) {
|
||||
var r map[string]interface{}
|
||||
var response NextCommandResponse
|
||||
err := json.NewDecoder(resp.Body).Decode(&r)
|
||||
if rs, ok := r["error"]; ok {
|
||||
response.Err = errors.New(rs.(string))
|
||||
}
|
||||
if rs, ok := r["total_payloads"]; ok {
|
||||
response.Total = int(rs.(float64))
|
||||
}
|
||||
if rs, ok := r["command_payload"]; ok {
|
||||
response.Payload = []byte(rs.(string))
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
func (r NextCommandResponse) error() error { return r.Err }
|
||||
|
||||
// DeleteCommandRequest is a request to delete a command
|
||||
type DeleteCommandRequest struct {
|
||||
// device UDID
|
||||
UDID string
|
||||
// command UUID
|
||||
UUID string
|
||||
}
|
||||
|
||||
// EncodeDeleteCommandRequest encodes a request for the NextCommand endpoint
|
||||
func EncodeDeleteCommandRequest(r *http.Request, request interface{}) error {
|
||||
req := request.(DeleteCommandRequest)
|
||||
path := r.URL.Path
|
||||
r.URL.Path = fmt.Sprintf("%v/%v/%v", path, req.UDID, req.UUID)
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(request); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(&buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeDeleteCommandRequest(r *http.Request) (interface{}, error) {
|
||||
func decodeDeleteCommandRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
vars := mux.Vars(r)
|
||||
udid, ok := vars["udid"]
|
||||
if !ok {
|
||||
@@ -137,115 +75,65 @@ func decodeDeleteCommandRequest(r *http.Request) (interface{}, error) {
|
||||
if !ok {
|
||||
return nil, errBadRouting
|
||||
}
|
||||
var request DeleteCommandRequest
|
||||
var request deleteCommandRequest
|
||||
request.UDID = udid
|
||||
request.UUID = uuid
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// DeleteCommandResponse is a response for a delete request
|
||||
type DeleteCommandResponse struct {
|
||||
Total int `json:"remaining_payloads"`
|
||||
Err error `json:"error,omitempty"`
|
||||
type errorer interface {
|
||||
error() error
|
||||
}
|
||||
|
||||
// DecodeDeleteCommandResponse decodes the response from the provided HTTP response,
|
||||
// simply by JSON decoding from the response body. It's designed to be used in
|
||||
// transport/http.Client.
|
||||
// first decode into map[string]interface{} and check for error in the response
|
||||
func DecodeDeleteCommandResponse(resp *http.Response) (interface{}, error) {
|
||||
var r map[string]interface{}
|
||||
var response DeleteCommandResponse
|
||||
err := json.NewDecoder(resp.Body).Decode(&r)
|
||||
if rs, ok := r["error"]; ok {
|
||||
response.Err = errors.New(rs.(string))
|
||||
}
|
||||
if rs, ok := r["remaining_payloads"]; ok {
|
||||
response.Total = int(rs.(float64))
|
||||
}
|
||||
return response, err
|
||||
type statuser interface {
|
||||
status() int
|
||||
}
|
||||
|
||||
func makeNewCommandEndpoint(svc MDMCommandService) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(NewCommandRequest)
|
||||
if req.UDID == "" || req.RequestType == "" {
|
||||
return NewCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
payload, err := svc.NewCommand(req.CommandRequest)
|
||||
if err != nil {
|
||||
return NewCommandResponse{Err: err}, nil
|
||||
}
|
||||
return NewCommandResponse{Payload: payload}, nil
|
||||
}
|
||||
type listEncoder interface {
|
||||
encodeList(w http.ResponseWriter) error
|
||||
}
|
||||
|
||||
func makeNextCommandEndpoint(svc MDMCommandService) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(NextCommandRequest)
|
||||
if req.UDID == "" {
|
||||
return NextCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
payload, total, err := svc.NextCommand(req.UDID)
|
||||
if err != nil {
|
||||
return NextCommandResponse{Err: err}, nil
|
||||
}
|
||||
return NextCommandResponse{Payload: payload, Total: total}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func makeDeleteCommandEndpoint(svc MDMCommandService) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(DeleteCommandRequest)
|
||||
if req.UDID == "" {
|
||||
return DeleteCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
if req.UUID == "" {
|
||||
return DeleteCommandResponse{Err: ErrEmptyRequest}, nil
|
||||
}
|
||||
total, err := svc.DeleteCommand(req.UDID, req.UUID)
|
||||
if err != nil {
|
||||
return DeleteCommandResponse{Err: err}, nil
|
||||
}
|
||||
return DeleteCommandResponse{Total: total}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// encodeResponse is the common method to encode all response types to the
|
||||
// client. I chose to do it this way because I didn't know if something more
|
||||
// specific was necessary. It's certainly possible to specialize on a
|
||||
// per-response (per-method) basis.
|
||||
func encodeResponse(w http.ResponseWriter, response interface{}) error {
|
||||
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
if e, ok := response.(errorer); ok && e.error() != nil {
|
||||
// Not a Go kit transport error, but a business-logic error.
|
||||
// Provide those as HTTP errors.
|
||||
encodeError(w, e.error())
|
||||
encodeError(ctx, e.error(), w)
|
||||
return nil
|
||||
}
|
||||
jsn, err := json.MarshalIndent(response, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
// for success responses
|
||||
if e, ok := response.(statuser); ok {
|
||||
w.WriteHeader(e.status())
|
||||
if e.status() == http.StatusNoContent {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
w.Write(jsn)
|
||||
return nil
|
||||
|
||||
// check if this is a collection
|
||||
if e, ok := response.(listEncoder); ok {
|
||||
return e.encodeList(w)
|
||||
|
||||
}
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func encodeError(w http.ResponseWriter, err error) {
|
||||
w.WriteHeader(codeFrom(err))
|
||||
response := map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
// encode errors from business-logic
|
||||
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
|
||||
// unwrap if the error is wrapped by kit http in it's own error type
|
||||
if httperr, ok := err.(kithttp.Error); ok {
|
||||
err = httperr.Err
|
||||
}
|
||||
jsn, err := json.MarshalIndent(response, "", " ")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
w.Write(jsn)
|
||||
}
|
||||
|
||||
func codeFrom(err error) int {
|
||||
switch err {
|
||||
// case ErrNotFound:
|
||||
// w.WriteHeader(http.StatusNotFound)
|
||||
// case errEmptyRequest, errBadUUID:
|
||||
// w.WriteHeader(http.StatusBadRequest)
|
||||
// case workflow.ErrExists:
|
||||
// w.WriteHeader(http.StatusConflict)
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user