mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-07 18:15:47 +08:00
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
45
platform/appstore/client.go
Normal file
45
platform/appstore/client.go
Normal 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
|
||||
}
|
||||
86
platform/appstore/get_apps.go
Normal file
86
platform/appstore/get_apps.go
Normal 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
|
||||
}
|
||||
45
platform/appstore/server.go
Normal file
45
platform/appstore/server.go
Normal 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
|
||||
}
|
||||
27
platform/appstore/service.go
Normal file
27
platform/appstore/service.go
Normal 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}
|
||||
}
|
||||
133
platform/appstore/upload_app.go
Normal file
133
platform/appstore/upload_app.go
Normal 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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package apply
|
||||
package appstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
Reference in New Issue
Block a user