From d51031a875b34dbc07bc1accaa4c92b77bcf0e2e Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Wed, 2 Oct 2024 23:47:33 -0400 Subject: [PATCH] feat: add service start/stop via daemon api --- daemon/api.go | 54 ++++++++++++++++++++++++-------- daemon/api_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/daemon/api.go b/daemon/api.go index ec52f77..6173ccd 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -2,28 +2,58 @@ package daemon import ( "context" + "encoding/json" "net/http" + service "spud/service" + service_definition "spud/service_definition" ) func GetApiRoutes() map[string]HandlerFuncWithContext { return map[string]HandlerFuncWithContext{ - "/service": ServiceList, + "/service/": ServiceList, + "/service/{serviceName}/": ServiceDetails, } } +type ServiceListPayload struct { + Definition service_definition.ServiceDefinition `json:"definition"` +} + +func handleServiceListPost(w http.ResponseWriter, r *http.Request, c context.Context) { + client := c.Value("client").(service.ServiceClient) + var p ServiceListPayload + json.NewDecoder(r.Body).Decode(&p) + client.Create(p.Definition) + w.WriteHeader(201) + json.NewEncoder(w).Encode(p.Definition) +} + +func handleServiceDetailDelete(w http.ResponseWriter, r *http.Request, c context.Context) { + client := c.Value("client").(service.ServiceClient) + serviceName := r.PathValue("serviceName") + client.Stop(serviceName) + + w.WriteHeader(204) +} + +func handleNotImplemented(w http.ResponseWriter) { + w.WriteHeader(501) +} + func ServiceList(w http.ResponseWriter, r *http.Request, c context.Context) { - var returnPayload []byte - var returnCode int - switch r.Method { - case "POST": - returnPayload = []byte("Create service: Not implemented") - returnCode = 501 + case http.MethodPost: + handleServiceListPost(w, r, c) default: - returnPayload = []byte("Not implemented") - returnCode = 501 + handleNotImplemented(w) + } +} + +func ServiceDetails(w http.ResponseWriter, r *http.Request, c context.Context) { + switch r.Method { + case http.MethodDelete: + handleServiceDetailDelete(w, r, c) + default: + handleNotImplemented(w) } - - w.WriteHeader(returnCode) - w.Write(returnPayload) } diff --git a/daemon/api_test.go b/daemon/api_test.go index 034f1bd..925e4fd 100644 --- a/daemon/api_test.go +++ b/daemon/api_test.go @@ -4,13 +4,48 @@ import ( "context" "net/http" "net/http/httptest" + service_definition "spud/service_definition" "testing" ) +type MockClient struct { + calls struct { + Create []service_definition.ServiceDefinition + Stop []string + } +} + +func (c *MockClient) Create(s service_definition.ServiceDefinition) { + c.calls.Create = append(c.calls.Create, s) +} + +func (c *MockClient) Stop(n string) { + c.calls.Stop = append(c.calls.Stop, n) +} + +func TestServiceListPostCreatesService(t *testing.T) { + mockClient := &MockClient{} + daemonContext := context.WithValue(context.Background(), "client", mockClient) + req := httptest.NewRequest(http.MethodPost, "/service/", nil) + resp := httptest.NewRecorder() + + ServiceList(resp, req, daemonContext) + + response := resp.Result() + + if response.StatusCode != 201 { + t.Errorf("Expected status 201, got %d", response.StatusCode) + } + + if len(mockClient.calls.Create) != 1 { + t.Error("Expected a call to Create") + } +} + func TestServiceListUnsupportedMethods(t *testing.T) { - for _, method := range []string{http.MethodGet, http.MethodPut, http.MethodHead} { + for _, method := range []string{http.MethodGet, http.MethodPut, http.MethodHead, http.MethodDelete} { t.Run(method, func(t *testing.T) { - req := httptest.NewRequest(method, "/service", nil) + req := httptest.NewRequest(method, "/service/", nil) resp := httptest.NewRecorder() ServiceList(resp, req, context.Background()) @@ -24,3 +59,40 @@ func TestServiceListUnsupportedMethods(t *testing.T) { } } + +func TestServiceDetailsDeleteStopsService(t *testing.T) { + mockClient := &MockClient{} + daemonContext := context.WithValue(context.Background(), "client", mockClient) + req := httptest.NewRequest(http.MethodDelete, "/service/service-name/", nil) + resp := httptest.NewRecorder() + + ServiceDetails(resp, req, daemonContext) + + response := resp.Result() + + if response.StatusCode != 204 { + t.Errorf("Expected status 204, got %d", response.StatusCode) + } + + if len(mockClient.calls.Stop) != 1 { + t.Error("Expected a call to Stop") + } +} + +func TestServiceDetailUnsupportedMethods(t *testing.T) { + for _, method := range []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodHead} { + t.Run(method, func(t *testing.T) { + req := httptest.NewRequest(method, "/service/service-name/", nil) + resp := httptest.NewRecorder() + + ServiceDetails(resp, req, context.Background()) + + response := resp.Result() + + if response.StatusCode != 501 { + t.Errorf("Expected status 501, got %d.", response.StatusCode) + } + }) + } + +}