refactor app upload APIs (#353)

Closes #296
This commit is contained in:
Victor Vrantchan
2017-12-10 01:21:59 -05:00
committed by GitHub
parent 5ff82344be
commit 431a09998c
19 changed files with 364 additions and 278 deletions

View File

@@ -106,7 +106,7 @@ Please rebuild the package and re-run the command.
if _, err := f.Seek(0, 0); err != nil {
return errors.Wrap(err, "reset pkg file reader")
}
err := cmd.applysvc.UploadApp(context.TODO(), nameMannifest(f.Name()), &buf, filepath.Base(f.Name()), f)
err := cmd.appsvc.UploadApp(context.TODO(), nameMannifest(f.Name()), &buf, filepath.Base(f.Name()), f)
if err != nil {
return err
}

View File

@@ -8,7 +8,7 @@ import (
"os"
"text/tabwriter"
"github.com/micromdm/micromdm/platform/api/server/list"
"github.com/micromdm/micromdm/platform/appstore"
)
type appsTableOutput struct{ w *tabwriter.Writer }
@@ -32,7 +32,7 @@ func (cmd *getCommand) getApps(args []string) error {
return err
}
ctx := context.Background()
apps, err := cmd.list.ListApplications(ctx, list.ListAppsOption{
apps, err := cmd.appsvc.ListApplications(ctx, appstore.ListAppsOption{
FilterName: []string{*flNameFilter},
})
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"github.com/micromdm/micromdm/platform/api/server/apply"
"github.com/micromdm/micromdm/platform/api/server/list"
"github.com/micromdm/micromdm/platform/appstore"
"github.com/micromdm/micromdm/platform/blueprint"
"github.com/micromdm/micromdm/platform/config"
"github.com/micromdm/micromdm/platform/profile"
@@ -19,6 +20,7 @@ type remoteServices struct {
blocksvc remove.Service
usersvc user.Service
configsvc config.Service
appsvc appstore.Service
applysvc apply.Service
list list.Service
}
@@ -64,6 +66,13 @@ func setupClient(logger log.Logger) (*remoteServices, error) {
return nil, err
}
appsvc, err := appstore.NewHTTPClient(
cfg.ServerURL, cfg.APIToken, logger,
httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify)))
if err != nil {
return nil, err
}
applysvc, err := apply.NewClient(
cfg.ServerURL, logger, cfg.APIToken,
httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify)))
@@ -84,6 +93,7 @@ func setupClient(logger log.Logger) (*remoteServices, error) {
blocksvc: blocksvc,
usersvc: usersvc,
configsvc: configsvc,
appsvc: appsvc,
applysvc: applysvc,
list: listsvc,
}, nil

View File

@@ -47,6 +47,7 @@ import (
"github.com/micromdm/micromdm/platform/api/server/list"
"github.com/micromdm/micromdm/platform/apns"
"github.com/micromdm/micromdm/platform/appstore"
appsbuiltin "github.com/micromdm/micromdm/platform/appstore/builtin"
"github.com/micromdm/micromdm/platform/blueprint"
blueprintbuiltin "github.com/micromdm/micromdm/platform/blueprint/builtin"
"github.com/micromdm/micromdm/platform/command"
@@ -256,7 +257,7 @@ func serve(args []string) error {
if err != nil {
stdlog.Fatalf("creating DEP client: %s\n", err)
}
appDB := &appstore.Repo{Path: *flRepoPath}
appDB := &appsbuiltin.Repo{Path: *flRepoPath}
var profilesvc profile.Service
{
@@ -287,12 +288,18 @@ func serve(args []string) error {
configEndpoints := config.MakeServerEndpoints(configsvc)
var appsvc appstore.Service
{
appsvc = appstore.New(appDB)
}
appEndpoints := appstore.MakeServerEndpoints(appsvc)
var listsvc list.Service
{
l := &list.ListService{
DEPClient: dc,
Devices: devDB,
Apps: appDB,
}
listsvc = l
@@ -310,14 +317,12 @@ func serve(args []string) error {
GetDEPAccountInfoEndpoint: list.MakeGetDEPAccountInfoEndpoint(listsvc),
GetDEPProfileEndpoint: list.MakeGetDEPProfileEndpoint(listsvc),
GetDEPDeviceEndpoint: list.MakeGetDEPDeviceDetailsEndpoint(listsvc),
ListAppsEndpont: list.MakeListAppsEndpoint(listsvc),
}
var applysvc apply.Service
{
l := &apply.ApplyService{
DEPClient: dc,
Apps: appDB,
}
applysvc = l
if err := l.WatchTokenUpdates(sm.pubclient); err != nil {
@@ -330,14 +335,8 @@ func serve(args []string) error {
defineDEPProfileEndpoint = apply.MakeDefineDEPProfile(applysvc)
}
var appUploadEndpoint endpoint.Endpoint
{
appUploadEndpoint = apply.MakeUploadAppEndpiont(applysvc)
}
applyEndpoints := apply.Endpoints{
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
AppUploadEndpoint: appUploadEndpoint,
}
applyAPIHandlers := apply.MakeHTTPHandlers(ctx, applyEndpoints, connectOpts...)
@@ -365,12 +364,14 @@ func serve(args []string) error {
blockhandler := block.MakeHTTPHandler(blockEndpoints, logger)
userHandler := user.MakeHTTPHandler(userEndpoints, logger)
configHandler := config.MakeHTTPHandler(configEndpoints, logger)
appsHandler := appstore.MakeHTTPHandler(appEndpoints, logger)
// API commands. Only handled if the user provides an api key.
if *flAPIKey != "" {
r.Handle("/v1/profiles", apiAuthMiddleware(*flAPIKey, profilesHandler))
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, blueprintsHandler))
r.Handle("/v1/users", apiAuthMiddleware(*flAPIKey, userHandler))
r.Handle("/v1/apps", apiAuthMiddleware(*flAPIKey, appsHandler))
r.Handle("/v1/devices/{udid}/block", apiAuthMiddleware(*flAPIKey, blockhandler))
r.Handle("/v1/devices/{udid}/unblock", apiAuthMiddleware(*flAPIKey, blockhandler))
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, configHandler))
@@ -383,8 +384,6 @@ func serve(args []string) error {
r.Handle("/v1/dep/account", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPAccountInfoHandler)).Methods("GET")
r.Handle("/v1/dep/profiles", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPProfileHandler)).Methods("GET")
r.Handle("/v1/dep/profiles", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DefineDEPProfileHandler)).Methods("POST")
r.Handle("/v1/apps", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.AppUploadHandler)).Methods("POST")
r.Handle("/v1/apps", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListAppsHandler)).Methods("GET")
}
if *flRepoPath != "" {

View File

@@ -27,20 +27,8 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
).Endpoint()
}
var uploadAppEndpoint endpoint.Endpoint
{
uploadAppEndpoint = httptransport.NewClient(
"POST",
copyURL(u, "/v1/apps"),
encodeRequestWithToken(token, EncodeUploadAppRequest),
DecodeUploadAppResponse,
opts...,
).Endpoint()
}
return Endpoints{
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
AppUploadEndpoint: uploadAppEndpoint,
}, nil
}

View File

@@ -2,7 +2,6 @@ package apply
import (
"context"
"io"
"github.com/go-kit/kit/endpoint"
"github.com/micromdm/dep"
@@ -10,21 +9,6 @@ import (
type Endpoints struct {
DefineDEPProfileEndpoint endpoint.Endpoint
AppUploadEndpoint endpoint.Endpoint
}
func (e Endpoints) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error {
request := appUploadRequest{
ManifestName: manifestName,
ManifestFile: manifest,
PKGFilename: pkgName,
PKGFile: pkg,
}
resp, err := e.AppUploadEndpoint(ctx, request)
if err != nil {
return err
}
return resp.(appUploadResponse).Err
}
func (e Endpoints) DefineDEPProfile(ctx context.Context, p *dep.Profile) (*dep.ProfileResponse, error) {
@@ -48,30 +32,6 @@ func MakeDefineDEPProfile(svc Service) endpoint.Endpoint {
}
}
func MakeUploadAppEndpiont(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(appUploadRequest)
err = svc.UploadApp(ctx, req.ManifestName, req.ManifestFile, req.PKGFilename, req.PKGFile)
return &appUploadResponse{
Err: err,
}, nil
}
}
type appUploadRequest struct {
ManifestName string
ManifestFile io.Reader
PKGFilename string
PKGFile io.Reader
}
type appUploadResponse struct {
Err error `json:"err,omitempty"`
}
func (r appUploadResponse) error() error { return r.Err }
type depProfileRequest struct{ *dep.Profile }
type depProfileResponse struct {
*dep.ProfileResponse

View File

@@ -3,43 +3,22 @@ package apply
import (
"context"
"encoding/json"
"io"
"log"
"sync"
"github.com/micromdm/dep"
"github.com/micromdm/micromdm/platform/appstore"
"github.com/micromdm/micromdm/platform/config"
"github.com/micromdm/micromdm/platform/pubsub"
)
type Service interface {
UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error
DEPService
}
type ApplyService struct {
mtx sync.RWMutex
DEPClient dep.Client
Apps appstore.AppStore
}
func (svc *ApplyService) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error {
if manifestName != "" {
if err := svc.Apps.SaveFile(manifestName, manifest); err != nil {
return err
}
}
if pkgName != "" {
if err := svc.Apps.SaveFile(pkgName, pkg); err != nil {
return err
}
}
return nil
}
func (svc *ApplyService) WatchTokenUpdates(pubsub pubsub.Subscriber) error {

View File

@@ -4,9 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
httptransport "github.com/go-kit/kit/transport/http"
@@ -26,12 +24,6 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
encodeResponse,
opts...,
),
AppUploadHandler: httptransport.NewServer(
endpoints.AppUploadEndpoint,
decodeAppUploadRequest,
encodeResponse,
opts...,
),
}
return h
}
@@ -44,64 +36,6 @@ func decodeDEPProfileRequest(ctx context.Context, r *http.Request) (interface{},
return req, nil
}
func decodeAppUploadRequest(ctx context.Context, r *http.Request) (interface{}, error) {
defer r.Body.Close()
appManifestFilename := r.FormValue("app_manifest_filename")
manifestFile, _, err := r.FormFile("app_manifest_filedata")
if err != nil && err != http.ErrMissingFile {
return nil, errors.Wrap(err, "manifest file")
}
pkgFilename := r.FormValue("pkg_name")
pkgFile, _, err := r.FormFile("pkg_filedata")
if err != nil && err != http.ErrMissingFile {
return nil, err
}
return appUploadRequest{
ManifestName: appManifestFilename,
ManifestFile: manifestFile,
PKGFilename: pkgFilename,
PKGFile: pkgFile,
}, nil
}
func EncodeUploadAppRequest(_ context.Context, r *http.Request, request interface{}) error {
req := request.(appUploadRequest)
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
if req.ManifestName != "" {
partManifest, err := writer.CreateFormFile("app_manifest_filedata", req.ManifestName)
if err != nil {
return err
}
_, err = io.Copy(partManifest, req.ManifestFile)
if err != nil {
return errors.Wrap(err, "copying appmanifest file to multipart writer")
}
writer.WriteField("app_manifest_filename", req.ManifestName)
}
if req.PKGFilename != "" {
partPkg, err := writer.CreateFormFile("pkg_filedata", req.PKGFilename)
if err != nil {
return err
}
_, err = io.Copy(partPkg, req.PKGFile)
if err != nil {
return errors.Wrap(err, "copying pkg file to multipart writer")
}
writer.WriteField("pkg_name", req.PKGFilename)
}
if err := writer.Close(); err != nil {
return errors.Wrap(err, "closing multipart writer")
}
r.Header.Set("Content-Type", writer.FormDataContentType())
r.Body = ioutil.NopCloser(body)
return nil
}
type errorWrapper struct {
Error string `json:"error"`
}
@@ -155,12 +89,3 @@ func DecodeDEPProfileResponse(_ context.Context, r *http.Response) (interface{},
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeUploadAppResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp appUploadResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

View File

@@ -60,23 +60,11 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
).Endpoint()
}
var listAppsEndpoint endpoint.Endpoint
{
listAppsEndpoint = httptransport.NewClient(
"GET",
copyURL(u, "/v1/apps"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeListAppsResponse,
opts...,
).Endpoint()
}
return Endpoints{
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPAccountInfoEndpoint: getDEPAccountInfoEndpoint,
GetDEPDeviceEndpoint: getDEPDeviceDetailsEndpoint,
GetDEPProfileEndpoint: getDEPProfilesEndpoint,
ListAppsEndpont: listAppsEndpoint,
}, nil
}

View File

@@ -13,7 +13,6 @@ type Endpoints struct {
GetDEPAccountInfoEndpoint endpoint.Endpoint
GetDEPDeviceEndpoint endpoint.Endpoint
GetDEPProfileEndpoint endpoint.Endpoint
ListAppsEndpont endpoint.Endpoint
}
func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]DeviceDTO, error) {
@@ -25,15 +24,6 @@ func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]D
return response.(devicesResponse).Devices, response.(devicesResponse).Err
}
func (e Endpoints) ListApplications(ctx context.Context, opts ListAppsOption) ([]AppDTO, error) {
request := appListRequest{opts}
response, err := e.ListAppsEndpont(ctx, request.Opts)
if err != nil {
return nil, err
}
return response.(appListResponse).Apps, response.(appListResponse).Err
}
func (e Endpoints) GetDEPAccountInfo(ctx context.Context) (*dep.Account, error) {
request := depAccountInforequest{}
response, err := e.GetDEPAccountInfoEndpoint(ctx, request)
@@ -63,17 +53,6 @@ func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint {
}
}
func MakeListAppsEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(appListRequest)
apps, err := svc.ListApplications(ctx, req.Opts)
return appListResponse{
Apps: apps,
Err: err,
}, nil
}
}
func (e Endpoints) GetDEPProfile(ctx context.Context, uuid string) (*dep.Profile, error) {
request := depProfileRequest{UUID: uuid}
response, err := e.GetDEPProfileEndpoint(ctx, request)
@@ -147,19 +126,3 @@ type depProfileResponse struct {
}
func (r depProfileResponse) error() error { return r.Err }
type appListRequest struct {
Opts ListAppsOption
}
type AppDTO struct {
Name string `json:"name"`
Payload []byte `json:"payload,omitempty"`
}
type appListResponse struct {
Apps []AppDTO `json:"apps,omitempty"`
Err error `json:"err,omitempty"`
}
func (r appListResponse) error() error { return r.Err }

View File

@@ -6,11 +6,8 @@ import (
"log"
"sync"
"github.com/groob/plist"
"github.com/micromdm/dep"
"github.com/pkg/errors"
"github.com/micromdm/micromdm/platform/appstore"
"github.com/micromdm/micromdm/platform/config"
"github.com/micromdm/micromdm/platform/device"
"github.com/micromdm/micromdm/platform/pubsub"
@@ -24,13 +21,8 @@ type ListDevicesOption struct {
FilterUDID []string
}
type ListAppsOption struct {
FilterName []string `json:"filter_name"`
}
type Service interface {
ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error)
ListApplications(ctx context.Context, opt ListAppsOption) ([]AppDTO, error)
DEPService
}
@@ -39,30 +31,6 @@ type ListService struct {
DEPClient dep.Client
Devices *device.DB
Apps appstore.AppStore
}
func (svc *ListService) ListApplications(ctx context.Context, opts ListAppsOption) ([]AppDTO, error) {
var filter string
if len(opts.FilterName) == 1 {
filter = opts.FilterName[0]
}
apps, err := svc.Apps.Apps(filter)
if err != nil {
return nil, err
}
var appList []AppDTO
for name, app := range apps {
payload, err := plist.MarshalIndent(&app, " ")
if err != nil {
return nil, errors.Wrap(err, "create dto payload")
}
appList = append(appList, AppDTO{
Name: name,
Payload: payload,
})
}
return appList, nil
}
func (svc *ListService) WatchTokenUpdates(pubsub pubsub.Subscriber) error {

View File

@@ -16,7 +16,6 @@ type HTTPHandlers struct {
GetDEPAccountInfoHandler http.Handler
GetDEPProfileHandler http.Handler
GetDEPDeviceDetailsHandler http.Handler
ListAppsHandler http.Handler
}
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
@@ -45,12 +44,6 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
encodeResponse,
opts...,
),
ListAppsHandler: httptransport.NewServer(
endpoints.ListAppsEndpont,
decodeListAppsRequest,
encodeResponse,
opts...,
),
}
return h
}
@@ -82,14 +75,6 @@ func decodeDEPProfileRequest(ctx context.Context, r *http.Request) (interface{},
return request, nil
}
func decodeListAppsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var request appListRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func errorDecoder(r *http.Response) error {
var w errorWrapper
if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
@@ -170,12 +155,3 @@ func DecodeDEPProfileResponse(_ context.Context, r *http.Response) (interface{},
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeListAppsResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp appListResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

View File

@@ -1,6 +1,6 @@
// package appstore provides an abstraction for uploading files and manifests
// to a repository.
package appstore
// package builtin provides an abstraction for uploading files and manifests
// to a file repository.
package builtin
import (
"io"
@@ -15,12 +15,6 @@ import (
"github.com/micromdm/micromdm/mdm/appmanifest"
)
type AppStore interface {
SaveFile(name string, f io.Reader) error
Manifest(name string) (*appmanifest.Manifest, error)
Apps(name string) (map[string]appmanifest.Manifest, error)
}
type Repo struct {
Path string
}

View File

@@ -0,0 +1,45 @@
package appstore
import (
"net/url"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/micromdm/micromdm/pkg/httputil"
)
func NewHTTPClient(instance, token string, logger log.Logger, opts ...httptransport.ClientOption) (Service, error) {
u, err := url.Parse(instance)
if err != nil {
return nil, err
}
var appUploadEndpoint endpoint.Endpoint
{
appUploadEndpoint = httptransport.NewClient(
"POST",
httputil.CopyURL(u, "/v1/apps"),
httputil.EncodeRequestWithToken(token, encodeUploadAppRequest),
decodeUploadAppResponse,
opts...,
).Endpoint()
}
var listAppsEndpoint endpoint.Endpoint
{
listAppsEndpoint = httptransport.NewClient(
"GET",
httputil.CopyURL(u, "/v1/apps"),
httputil.EncodeRequestWithToken(token, httptransport.EncodeJSONRequest),
decodeListAppsResponse,
opts...,
).Endpoint()
}
return Endpoints{
AppUploadEndpoint: appUploadEndpoint,
ListAppsEndpoint: listAppsEndpoint,
}, nil
}

View File

@@ -0,0 +1,86 @@
package appstore
import (
"context"
"net/http"
"github.com/go-kit/kit/endpoint"
"github.com/groob/plist"
"github.com/micromdm/micromdm/pkg/httputil"
"github.com/pkg/errors"
)
type ListAppsOption struct {
FilterName []string `json:"filter_name"`
}
type AppDTO struct {
Name string `json:"name"`
Payload []byte `json:"payload,omitempty"`
}
func (svc *AppService) ListApplications(ctx context.Context, opts ListAppsOption) ([]AppDTO, error) {
var filter string
if len(opts.FilterName) == 1 {
filter = opts.FilterName[0]
}
apps, err := svc.store.Apps(filter)
if err != nil {
return nil, err
}
var appList []AppDTO
for name, app := range apps {
payload, err := plist.MarshalIndent(&app, " ")
if err != nil {
return nil, errors.Wrap(err, "create dto payload")
}
appList = append(appList, AppDTO{
Name: name,
Payload: payload,
})
}
return appList, nil
}
type appListRequest struct {
Opts ListAppsOption
}
type appListResponse struct {
Apps []AppDTO `json:"apps,omitempty"`
Err error `json:"err,omitempty"`
}
func (r appListResponse) Failed() error { return r.Err }
func decodeListAppsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var req appListRequest
err := httputil.DecodeJSONRequest(r, &req)
return req, err
}
func decodeListAppsResponse(_ context.Context, r *http.Response) (interface{}, error) {
var resp appListResponse
err := httputil.DecodeJSONResponse(r, &resp)
return resp, err
}
func MakeListAppsEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(appListRequest)
apps, err := svc.ListApplications(ctx, req.Opts)
return appListResponse{
Apps: apps,
Err: err,
}, nil
}
}
func (e Endpoints) ListApplications(ctx context.Context, opts ListAppsOption) ([]AppDTO, error) {
request := appListRequest{opts}
response, err := e.ListAppsEndpoint(ctx, request.Opts)
if err != nil {
return nil, err
}
return response.(appListResponse).Apps, response.(appListResponse).Err
}

View File

@@ -0,0 +1,45 @@
package appstore
import (
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/gorilla/mux"
"github.com/micromdm/micromdm/pkg/httputil"
)
type Endpoints struct {
AppUploadEndpoint endpoint.Endpoint
ListAppsEndpoint endpoint.Endpoint
}
func MakeServerEndpoints(s Service) Endpoints {
return Endpoints{
AppUploadEndpoint: MakeUploadAppEndpiont(s),
ListAppsEndpoint: MakeListAppsEndpoint(s),
}
}
func MakeHTTPHandler(e Endpoints, logger log.Logger) *mux.Router {
r, options := httputil.NewRouter(logger)
// POST /v1/apps upload an app to the server
// GET /v1/apps list apps managed by the server
r.Methods("POST").Path("/v1/apps").Handler(httptransport.NewServer(
e.AppUploadEndpoint,
decodeAppUploadRequest,
httputil.EncodeJSONResponse,
options...,
))
r.Methods("GET").Path("/v1/apps").Handler(httptransport.NewServer(
e.ListAppsEndpoint,
decodeListAppsRequest,
httputil.EncodeJSONResponse,
options...,
))
return r
}

View File

@@ -0,0 +1,27 @@
package appstore
import (
"context"
"io"
"github.com/micromdm/micromdm/mdm/appmanifest"
)
type Service interface {
UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error
ListApplications(ctx context.Context, opt ListAppsOption) ([]AppDTO, error)
}
type AppService struct {
store Store
}
type Store interface {
SaveFile(name string, f io.Reader) error
Manifest(name string) (*appmanifest.Manifest, error)
Apps(name string) (map[string]appmanifest.Manifest, error)
}
func New(store Store) *AppService {
return &AppService{store: store}
}

View File

@@ -0,0 +1,133 @@
package appstore
import (
"bytes"
"context"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"github.com/go-kit/kit/endpoint"
"github.com/pkg/errors"
"github.com/micromdm/micromdm/pkg/httputil"
)
func (svc *AppService) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error {
if manifestName != "" {
if err := svc.store.SaveFile(manifestName, manifest); err != nil {
return err
}
}
if pkgName != "" {
if err := svc.store.SaveFile(pkgName, pkg); err != nil {
return err
}
}
return nil
}
type appUploadRequest struct {
ManifestName string
ManifestFile io.Reader
PKGFilename string
PKGFile io.Reader
}
type appUploadResponse struct {
Err error `json:"err,omitempty"`
}
func (r appUploadResponse) Failed() error { return r.Err }
func decodeAppUploadRequest(ctx context.Context, r *http.Request) (interface{}, error) {
defer r.Body.Close()
appManifestFilename := r.FormValue("app_manifest_filename")
manifestFile, _, err := r.FormFile("app_manifest_filedata")
if err != nil && err != http.ErrMissingFile {
return nil, errors.Wrap(err, "manifest file")
}
pkgFilename := r.FormValue("pkg_name")
pkgFile, _, err := r.FormFile("pkg_filedata")
if err != nil && err != http.ErrMissingFile {
return nil, err
}
return appUploadRequest{
ManifestName: appManifestFilename,
ManifestFile: manifestFile,
PKGFilename: pkgFilename,
PKGFile: pkgFile,
}, nil
}
func encodeUploadAppRequest(_ context.Context, r *http.Request, request interface{}) error {
req := request.(appUploadRequest)
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
if req.ManifestName != "" {
partManifest, err := writer.CreateFormFile("app_manifest_filedata", req.ManifestName)
if err != nil {
return err
}
_, err = io.Copy(partManifest, req.ManifestFile)
if err != nil {
return errors.Wrap(err, "copying appmanifest file to multipart writer")
}
writer.WriteField("app_manifest_filename", req.ManifestName)
}
if req.PKGFilename != "" {
partPkg, err := writer.CreateFormFile("pkg_filedata", req.PKGFilename)
if err != nil {
return err
}
_, err = io.Copy(partPkg, req.PKGFile)
if err != nil {
return errors.Wrap(err, "copying pkg file to multipart writer")
}
writer.WriteField("pkg_name", req.PKGFilename)
}
if err := writer.Close(); err != nil {
return errors.Wrap(err, "closing multipart writer")
}
r.Header.Set("Content-Type", writer.FormDataContentType())
r.Body = ioutil.NopCloser(body)
return nil
}
func decodeUploadAppResponse(_ context.Context, r *http.Response) (interface{}, error) {
var resp appUploadResponse
err := httputil.DecodeJSONResponse(r, &resp)
return resp, err
}
func MakeUploadAppEndpiont(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(appUploadRequest)
err = svc.UploadApp(ctx, req.ManifestName, req.ManifestFile, req.PKGFilename, req.PKGFile)
return &appUploadResponse{
Err: err,
}, nil
}
}
func (e Endpoints) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error {
request := appUploadRequest{
ManifestName: manifestName,
ManifestFile: manifest,
PKGFilename: pkgName,
PKGFile: pkg,
}
resp, err := e.AppUploadEndpoint(ctx, request)
if err != nil {
return err
}
return resp.(appUploadResponse).Err
}

View File

@@ -1,4 +1,4 @@
package apply
package appstore
import (
"bytes"