diff --git a/munki/datastore/manifest.go b/munki/datastore/manifest.go index 23c3a20..cc796f0 100644 --- a/munki/datastore/manifest.go +++ b/munki/datastore/manifest.go @@ -47,11 +47,11 @@ func (r *SimpleRepo) NewManifest(name string) (*munki.Manifest, error) { } // SaveManifest saves a manifest to the datastore -func (r *SimpleRepo) SaveManifest(manifest *munki.Manifest) error { - if manifest.Filename == "" { - return errors.New("filename key must be set") +func (r *SimpleRepo) SaveManifest(path string, manifest *munki.Manifest) error { + if path == "" { + return errors.New("must specify a manifest name") } - manifestPath := fmt.Sprintf("%v/manifests/%v", r.Path, manifest.Filename) + manifestPath := fmt.Sprintf("%v/manifests/%v", r.Path, path) file, err := os.OpenFile(manifestPath, os.O_WRONLY, 0755) if err != nil { return err diff --git a/munki/munki/manifest.go b/munki/munki/manifest.go index ab68644..92f70aa 100644 --- a/munki/munki/manifest.go +++ b/munki/munki/manifest.go @@ -5,7 +5,7 @@ type ManifestStore interface { AllManifests() (*ManifestCollection, error) Manifest(name string) (*Manifest, error) NewManifest(name string) (*Manifest, error) - SaveManifest(manifest *Manifest) error + SaveManifest(path string, manifest *Manifest) error DeleteManifest(name string) error } diff --git a/munki/server/endpoint.go b/munki/server/endpoint.go index a5d3485..63a5854 100644 --- a/munki/server/endpoint.go +++ b/munki/server/endpoint.go @@ -103,3 +103,23 @@ func makeReplaceManifestEndpoint(svc Service) endpoint.Endpoint { return replaceManifestResponse{Manifest: manifest, Err: err}, nil } } + +type updateManifestRequest struct { + Path string `plist:"filename" json:"filename"` + *munki.ManifestPayload +} + +type updateManifestResponse struct { + *munki.Manifest + Err error `json:"error,omitempty" plist:"error,omitempty"` +} + +func (r updateManifestResponse) error() error { return r.Err } + +func makeUpdateManifestEndpoint(svc Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(updateManifestRequest) + manifest, err := svc.UpdateManifest(ctx, req.Path, req.ManifestPayload) + return updateManifestResponse{Manifest: manifest, Err: err}, nil + } +} diff --git a/munki/server/service.go b/munki/server/service.go index 13fc605..a575d71 100644 --- a/munki/server/service.go +++ b/munki/server/service.go @@ -13,6 +13,7 @@ type Service interface { CreateManifest(ctx context.Context, name string, manifest *munki.Manifest) (*munki.Manifest, error) ReplaceManifest(ctx context.Context, name string, manifest *munki.Manifest) (*munki.Manifest, error) DeleteManifest(ctx context.Context, name string) error + UpdateManifest(ctx context.Context, name string, payload *munki.ManifestPayload) (*munki.Manifest, error) } type service struct { @@ -32,8 +33,7 @@ func (svc service) CreateManifest(ctx context.Context, name string, manifest *mu if err != nil { return nil, err } - manifest.Filename = name - if err := svc.repo.SaveManifest(manifest); err != nil { + if err := svc.repo.SaveManifest(name, manifest); err != nil { return nil, err } return manifest, nil @@ -50,8 +50,16 @@ func (svc service) ReplaceManifest(ctx context.Context, name string, manifest *m return svc.CreateManifest(ctx, name, manifest) } -func (svc service) UpdateManifest(ctx context.Context, name string, manifest *munki.Manifest) (*munki.Manifest, error) { - panic("not implemented") +func (svc service) UpdateManifest(ctx context.Context, name string, payload *munki.ManifestPayload) (*munki.Manifest, error) { + manifest, err := svc.repo.Manifest(name) + if err != nil { + return nil, err + } + manifest.UpdateFromPayload(payload) + if err := svc.repo.SaveManifest(name, manifest); err != nil { + return nil, err + } + return manifest, nil } // NewService creates a new munki api service diff --git a/munki/server/transport.go b/munki/server/transport.go index 3ed35a6..a8cc908 100644 --- a/munki/server/transport.go +++ b/munki/server/transport.go @@ -65,6 +65,20 @@ func decodeReplaceManifestRequest(_ context.Context, r *http.Request) (interface return request, nil } +func decodeUpdateManifestRequest(_ context.Context, r *http.Request) (interface{}, error) { + var request updateManifestRequest + if err := json.NewDecoder(r.Body).Decode(&request.ManifestPayload); err != nil { + return nil, err + } + vars := mux.Vars(r) + path, ok := vars["path"] + if !ok { + return nil, errBadRouting + } + request.Path = path + return request, nil +} + // ServiceHandler creates an HTTP handler for the munki Service func ServiceHandler(ctx context.Context, svc Service, logger kitlog.Logger) http.Handler { opts := []kithttp.ServerOption{ @@ -107,6 +121,13 @@ func ServiceHandler(ctx context.Context, svc Service, logger kitlog.Logger) http encodeResponse, opts..., ) + updateManifestHandler := kithttp.NewServer( + ctx, + makeUpdateManifestEndpoint(svc), + decodeUpdateManifestRequest, + encodeResponse, + opts..., + ) r := mux.NewRouter() // manifests r.Handle("/api/v1/manifests/{path}", showManifestHandler).Methods("GET") @@ -114,6 +135,7 @@ func ServiceHandler(ctx context.Context, svc Service, logger kitlog.Logger) http r.Handle("/api/v1/manifests", createManifestHandler).Methods("POST") r.Handle("/api/v1/manifests/{path}", deleteManifestHandler).Methods("DELETE") r.Handle("/api/v1/manifests/{path}", replaceManifestHandler).Methods("PUT") + r.Handle("/api/v1/manifests/{path}", updateManifestHandler).Methods("PATCH") return r } diff --git a/munki/server/transport_test.go b/munki/server/transport_test.go index 5c37b13..57912a4 100644 --- a/munki/server/transport_test.go +++ b/munki/server/transport_test.go @@ -32,6 +32,27 @@ func TestShowManifests(t *testing.T) { testShowManifestHTTP(t, server, "site_none", http.StatusNotFound) } +func TestUpdateManifest(t *testing.T) { + server, _ := newServer(t) + defer server.Close() + manifests := []*munki.Manifest{ + &munki.Manifest{ + Filename: "update-manifest", + Catalogs: []string{"production", "testing"}, + }, + } + + for _, m := range manifests { + os.Remove("testdata/testrepo/manifests/" + m.Filename) + testCreateManifestHTTP(t, server, m.Filename, m, http.StatusOK) + m1 := &munki.ManifestPayload{ + Catalogs: &[]string{"foo"}, + } + testUpdateManifestHTTP(t, server, m.Filename, m1, http.StatusOK) + os.Remove("testdata/testrepo/manifests/" + m.Filename) + } +} + func TestReplaceManifest(t *testing.T) { server, _ := newServer(t) defer server.Close() @@ -134,6 +155,29 @@ func testReplaceManifestHTTP(t *testing.T, server *httptest.Server, path string, } } +func testUpdateManifestHTTP(t *testing.T, server *httptest.Server, path string, m *munki.ManifestPayload, expectedStatus int) { + client := http.DefaultClient + theURL := server.URL + "/api/v1/manifests/" + path + data, err := json.Marshal(m) + if err != nil { + t.Fatal(err) + } + body := ioutil.NopCloser(bytes.NewBuffer(data)) + req, err := http.NewRequest("PATCH", theURL, body) + if err != nil { + t.Fatal(err) + } + resp, err := client.Do(req) + if err != nil { + t.Fatal(err) + } + if resp.StatusCode != expectedStatus { + fmt.Println(theURL) + io.Copy(os.Stdout, resp.Body) + t.Fatal("expected", expectedStatus, "got", resp.StatusCode) + } +} + func testDeleteManifestHTTP(t *testing.T, server *httptest.Server, path string, expectedStatus int) { client := http.DefaultClient theURL := server.URL + "/api/v1/manifests/" + path