From b52821e2164e334b54a4652457352699f5101055 Mon Sep 17 00:00:00 2001 From: Punit Kulal Date: Mon, 26 Apr 2021 17:13:14 +0530 Subject: [PATCH] Feature/update routes (#10) API routes to follow REST resource convention --- .gitignore | 1 + Makefile | 2 +- api/install/install.go | 80 ++++- api/install/install_api_test.go | 51 ++- api/list/list.go | 161 +++++++-- api/list/list_api_test.go | 122 ++++++- api/list/service.go | 8 +- api/list/service_test.go | 14 +- api/uninstall/uninstall.go | 96 ++++-- api/uninstall/uninstall_test.go | 81 ++++- api/upgrade/upgrade.go | 59 +++- api/upgrade/upgrade_api_test.go | 54 ++- cmd/albatross/albatross.go | 9 +- docs/swagger.json | 592 ++++++++++++++++++-------------- go.mod | 3 +- go.sum | 4 + pkg/helmcli/config/config.go | 2 +- pkg/helmcli/flags/flags.go | 5 +- swagger/release.go | 19 + 19 files changed, 945 insertions(+), 418 deletions(-) diff --git a/.gitignore b/.gitignore index 3198ee5..29fd1cc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dist/ bin/ vendor/ *.swp +cmd/albatross/__debug_bin diff --git a/Makefile b/Makefile index 5ae249c..1c22c6f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: clean check-quality build test golangci +all: clean check-quality build test golangci update-doc ALL_PACKAGES=$(shell go list ./...) SOURCE_DIRS=$(shell go list ./... | cut -d "/" -f4 | uniq) diff --git a/api/install/install.go b/api/install/install.go index 86fd025..f6ec90d 100644 --- a/api/install/install.go +++ b/api/install/install.go @@ -10,13 +10,21 @@ import ( "github.com/gojekfarm/albatross/pkg/helmcli/flags" "github.com/gojekfarm/albatross/pkg/logger" + + "github.com/gorilla/mux" +) + +const ( + CLUSTER string = "cluster" + NAMESPACE string = "namespace" + RELEASE string = "release_name" + alreadyPresent string = "cannot re-use a name that is still in use" ) // Request is the body for insatlling a release // swagger:model installRequestBody type Request struct { - // example: mysql - Name string `json:"name"` + Name string `json:"-"` // example: stable/mysql Chart string `json:"chart"` // example: {"replicaCount": 1} @@ -69,19 +77,52 @@ type service interface { } // Handler handles an install request -// swagger:route PUT /install installRelease +// swagger:operation PUT /clusters/{cluster}/namespaces/{namespace}/releases/{release_name} release installOperation // -// Installs a helm release as specified in the request // +// --- +// summary: Install helm release at the specified cluster and namespace // consumes: -// - application/json +// - application/json // produces: -// - application/json -// schemes: http +// - application/json +// parameters: +// - name: cluster +// in: path +// required: true +// default: minikube +// type: string +// format: string +// - name: namespace +// in: path +// required: true +// default: default +// type: string +// format: string +// - name: release_name +// in: path +// required: true +// type: string +// format: string +// default: mysql-final +// - name: Body +// in: body +// required: true +// schema: +// "$ref": "#/definitions/installRequestBody" +// schemes: +// - http // responses: -// 200: installResponse -// 400: installResponse -// 500: installResponse +// '200': +// "$ref": "#/responses/installResponse" +// '400': +// description: Invalid request +// '409': +// schema: +// $ref: "#/definitions/installResponseErrorBody" +// '500': +// "$ref": "#/responses/installResponse" + func Handler(service service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() @@ -92,24 +133,33 @@ func Handler(service service) http.Handler { w.WriteHeader(http.StatusBadRequest) return } - + values := mux.Vars(r) + req.Flags.KubeContext = values["cluster"] + req.Flags.Namespace = values["namespace"] + req.Name = values["release_name"] resp, err := service.Install(r.Context(), req) if err != nil { - respondInstallError(w, "error while installing chart: %v", err) + code := http.StatusInternalServerError + if err.Error() == alreadyPresent { + code = http.StatusConflict + } + respondInstallError(w, "error while installing chart: %v", err, code) return } if err := json.NewEncoder(w).Encode(&resp); err != nil { - respondInstallError(w, "error writing response: %v", err) + respondInstallError(w, "error writing response: %v", err, http.StatusInternalServerError) return } }) } // TODO: This does not handle different status codes. -func respondInstallError(w http.ResponseWriter, logprefix string, err error) { +func respondInstallError(w http.ResponseWriter, logprefix string, err error, statusCode int) { response := Response{Error: err.Error()} - w.WriteHeader(http.StatusInternalServerError) + if statusCode > 0 { + w.WriteHeader(statusCode) + } if err := json.NewEncoder(w).Encode(&response); err != nil { logger.Errorf("[Install] %s %v", logprefix, err) w.WriteHeader(http.StatusInternalServerError) diff --git a/api/install/install_api_test.go b/api/install/install_api_test.go index 3237d0c..c003a67 100644 --- a/api/install/install_api_test.go +++ b/api/install/install_api_test.go @@ -10,11 +10,13 @@ import ( "strings" "testing" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/gojekfarm/albatross/pkg/helmcli/flags" "github.com/gojekfarm/albatross/pkg/logger" "helm.sh/helm/v3/pkg/release" @@ -43,20 +45,33 @@ func (s *InstallerTestSuite) SetupSuite() { func (s *InstallerTestSuite) SetupTest() { s.recorder = httptest.NewRecorder() s.mockService = new(mockService) - handler := Handler(s.mockService) - s.server = httptest.NewServer(handler) + router := mux.NewRouter() + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases/{release_name}", Handler(s.mockService)).Methods(http.MethodPut) + s.server = httptest.NewServer(router) } func (s *InstallerTestSuite) TestShouldReturnDeployedStatusOnSuccessfulInstall() { chartName := "stable/redis-ha" - body := fmt.Sprintf(`{"chart":"%s", "name": "redis-v5", "values": {"replicas": 2}, "flags": {"namespace": "albatross"}}`, chartName) + body := fmt.Sprintf(`{"chart":"%s", "values": {"replicas": 2}, "flags": {}}`, chartName) - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) + req, _ := http.NewRequest("PUT", fmt.Sprintf("%s/clusters/minikube/namespaces/albatross/releases/redis-v5", s.server.URL), strings.NewReader(body)) response := Response{ Status: release.StatusDeployed.String(), } - - s.mockService.On("Install", mock.Anything, mock.AnythingOfType("Request")).Return(response, nil) + requestStruct := Request{ + Chart: chartName, + Name: "redis-v5", + Values: map[string]interface{}{ + "replicas": float64(2), + }, + Flags: Flags{ + GlobalFlags: flags.GlobalFlags{ + Namespace: "albatross", + KubeContext: "minikube", + }, + }, + } + s.mockService.On("Install", mock.Anything, requestStruct).Return(response, nil) resp, err := http.DefaultClient.Do(req) assert.Equal(s.T(), http.StatusOK, resp.StatusCode) @@ -69,10 +84,20 @@ func (s *InstallerTestSuite) TestShouldReturnDeployedStatusOnSuccessfulInstall() func (s *InstallerTestSuite) TestShouldReturnInternalServerErrorOnFailure() { chartName := "stable/redis-ha" - body := fmt.Sprintf(`{"chart":"%s", "name": "redis-v5"}`, chartName) - - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) - s.mockService.On("Install", mock.Anything, mock.AnythingOfType("Request")).Return(Response{}, errors.New("invalid chart")) + body := fmt.Sprintf(`{"chart":"%s"}`, chartName) + + req, _ := http.NewRequest("PUT", fmt.Sprintf("%s/clusters/minikube/namespaces/albatross/releases/redis-v5", s.server.URL), strings.NewReader(body)) + requestStruct := Request{ + Chart: chartName, + Name: "redis-v5", + Flags: Flags{ + GlobalFlags: flags.GlobalFlags{ + Namespace: "albatross", + KubeContext: "minikube", + }, + }, + } + s.mockService.On("Install", mock.Anything, requestStruct).Return(Response{}, errors.New("invalid chart")) resp, err := http.DefaultClient.Do(req) @@ -86,15 +111,13 @@ func (s *InstallerTestSuite) TestShouldReturnInternalServerErrorOnFailure() { func (s *InstallerTestSuite) TestReturnShouldBadRequestOnInvalidRequest() { chartName := "stable/redis-ha" - body := fmt.Sprintf(`{"chart":"%s", "name": "redis-v5}`, chartName) + body := fmt.Sprintf(`{"chart":"%s}`, chartName) - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) - s.mockService.On("Install", mock.Anything, mock.AnythingOfType("Request")).Return(Release{}, nil) + req, _ := http.NewRequest("PUT", fmt.Sprintf("%s/clusters/minikube/namespaces/albatross/releases/redis-v5", s.server.URL), strings.NewReader(body)) resp, err := http.DefaultClient.Do(req) assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) require.NoError(s.T(), err) - s.mockService.AssertNotCalled(s.T(), "Install") } func (s *InstallerTestSuite) TearDownTest() { diff --git a/api/list/list.go b/api/list/list.go index 19c2a26..c491552 100644 --- a/api/list/list.go +++ b/api/list/list.go @@ -3,7 +3,6 @@ package list import ( "context" "encoding/json" - "io" "net/http" "time" @@ -11,35 +10,24 @@ import ( "github.com/gojekfarm/albatross/pkg/helmcli/flags" "github.com/gojekfarm/albatross/pkg/logger" + + "github.com/gorilla/mux" + "github.com/gorilla/schema" ) -// Request is body of List Route -// swagger:model listRequestBody +var decoder *schema.Decoder = schema.NewDecoder() + type Request struct { Flags } -// Flags contains all the params supported -// swagger:model listRequestFlags type Flags struct { - // example: false - // required: false - AllNamespaces bool `json:"all-namespaces,omitempty"` - // required: false - // example: false - Deployed bool `json:"deployed,omitempty"` - // required: false - // example: false - Failed bool `json:"failed,omitempty"` - // required: false - // example: false - Pending bool `json:"pending,omitempty"` - // required: false - // example: false - Uninstalled bool `json:"uninstalled,omitempty"` - // required: false - // example: false - Uninstalling bool `json:"uninstalling,omitempty"` + AllNamespaces bool `schema:"-"` + Deployed bool `schema:"deployed"` + Failed bool `schema:"failed"` + Pending bool `schema:"pending"` + Uninstalled bool `schema:"uninstalled"` + Uninstalling bool `schema:"uninstalling"` flags.GlobalFlags } @@ -75,36 +63,76 @@ type service interface { } // Handler handles a list request -// swagger:route GET /list listRelease +// swagger:operation GET /clusters/{cluster}/releases release listOperation // -// List helm releases as specified in the request // -// consumes: -// - application/json +// --- +// summary: List the helm releases for the cluster // produces: -// - application/json -// schemes: http +// - application/json +// parameters: +// - name: cluster +// in: path +// required: true +// default: minikube +// type: string +// format: string +// - name: deployed +// in: query +// type: boolean +// default: false +// - name: uninstalled +// in: query +// type: boolean +// default: false +// - name: failed +// in: query +// type: boolean +// default: false +// - name: pending +// in: query +// type: boolean +// default: false +// - name: uninstalling +// in: query +// type: boolean +// default: false +// schemes: +// - http // responses: -// 200: listResponse -// 400: listResponse -// 500: listResponse +// '200': +// "$ref": "#/responses/listResponse" +// '204': +// description: No releases found +// '400': +// schema: +// $ref: "#/definitions/listErrorResponse" +// '500': +// schema: +// $ref: "#/definitions/listErrorResponse" func Handler(service service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - var req Request - if err := json.NewDecoder(r.Body).Decode(&req); err == io.EOF || err != nil { + if err := decoder.Decode(&req, r.URL.Query()); err != nil { logger.Errorf("[List] error decoding request: %v", err.Error()) w.WriteHeader(http.StatusBadRequest) return } - + values := mux.Vars(r) + req.KubeContext = values["cluster"] + populateRequestFlags(&req, values) resp, err := service.List(r.Context(), req) if err != nil { respondListError(w, "error while listing charts: %v", err) return } + if resp.Releases == nil || len(resp.Releases) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + if err = json.NewEncoder(w).Encode(resp); err != nil { respondListError(w, "error writing response: %v", err) return @@ -112,6 +140,61 @@ func Handler(service service) http.Handler { }) } +// Handler handles a list request +// swagger:operation GET /clusters/{cluster}/namespaces/{namespace}/releases release listOperationWithNamespace +// +// +// --- +// summary: List the helm releases for the cluster and namespace +// produces: +// - application/json +// parameters: +// - name: cluster +// in: path +// required: true +// default: minikube +// type: string +// format: string +// - name: namespace +// in: path +// required: true +// type: string +// format: string +// default: default +// - name: deployed +// in: query +// type: boolean +// default: false +// - name: uninstalled +// in: query +// type: boolean +// default: false +// - name: failed +// in: query +// type: boolean +// default: false +// - name: pending +// in: query +// type: boolean +// default: false +// - name: uninstalling +// in: query +// type: boolean +// default: false +// schemes: +// - http +// responses: +// '200': +// "$ref": "#/responses/listResponse" +// '204': +// description: No releases found +// '400': +// "$ref": "#/responses/listResponse" +// '404': +// "$ref": "#/responses/listResponse" +// '500': +// "$ref": "#/responses/listResponse" + func respondListError(w http.ResponseWriter, logprefix string, err error) { response := Response{Error: err.Error()} w.WriteHeader(http.StatusInternalServerError) @@ -121,3 +204,11 @@ func respondListError(w http.ResponseWriter, logprefix string, err error) { return } } + +func populateRequestFlags(req *Request, values map[string]string) { + if values["namespace"] == "" { + req.AllNamespaces = true + } else { + req.Namespace = values["namespace"] + } +} diff --git a/api/list/list_api_test.go b/api/list/list_api_test.go index 21f77fb..714721e 100644 --- a/api/list/list_api_test.go +++ b/api/list/list_api_test.go @@ -3,18 +3,20 @@ package list import ( "context" "encoding/json" + "errors" "fmt" "net/http" "net/http/httptest" - "strings" "testing" "time" + "github.com/gorilla/mux" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "gotest.tools/assert" + "github.com/gojekfarm/albatross/pkg/helmcli/flags" "github.com/gojekfarm/albatross/pkg/logger" "helm.sh/helm/v3/pkg/release" @@ -43,17 +45,71 @@ func (s *ListTestSuite) SetupSuite() { func (s *ListTestSuite) SetupTest() { s.recorder = httptest.NewRecorder() s.mockService = new(mockService) - handler := Handler(s.mockService) - s.server = httptest.NewServer(handler) + router := mux.NewRouter() + router.Handle("/clusters/{cluster}/releases", Handler(s.mockService)).Methods(http.MethodGet) + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases", Handler(s.mockService)).Methods(http.MethodGet) + s.server = httptest.NewServer(router) } func (s *ListTestSuite) TestShouldReturnReleasesWhenSuccessfulAPICall() { layout := "2006-01-02T15:04:05.000Z" str := "2014-11-12T11:45:26.371Z" timeFromStr, _ := time.Parse(layout, str) - body := `{"release_status":"deployed"}` - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/list", s.server.URL), strings.NewReader(body)) + req, _ := http.NewRequest("GET", fmt.Sprintf("%s/clusters/staging/releases?deployed=true", s.server.URL), nil) + expectedRequestStruct := Request{ + Flags: Flags{ + AllNamespaces: true, + Deployed: true, + GlobalFlags: flags.GlobalFlags{ + KubeContext: "staging", + }, + }, + } + response := Response{ + Releases: []Release{ + { + Name: "test-release", + Namespace: "test", + Version: 1, + Updated: timeFromStr, + Status: release.StatusDeployed, + AppVersion: "0.1", + }, + }, + } + s.mockService.On("List", mock.Anything, expectedRequestStruct).Return(response, nil) + + res, err := http.DefaultClient.Do(req) + assert.Equal(s.T(), 200, res.StatusCode) + require.NoError(s.T(), err) + + var actualResponse Response + err = json.NewDecoder(res.Body).Decode(&actualResponse) + + expectedResponse := Response{ + Error: "", + Releases: response.Releases, + } + + assert.Equal(s.T(), expectedResponse.Releases[0], actualResponse.Releases[0]) + require.NoError(s.T(), err) + s.mockService.AssertExpectations(s.T()) +} +func (s *ListTestSuite) TestShouldReturnReleasesWhenSuccessfulAPICallNamespace() { + layout := "2006-01-02T15:04:05.000Z" + str := "2014-11-12T11:45:26.371Z" + timeFromStr, _ := time.Parse(layout, str) + req, _ := http.NewRequest("GET", fmt.Sprintf("%s/clusters/staging/namespaces/test/releases?deployed=true", s.server.URL), nil) + expectedRequestStruct := Request{ + Flags: Flags{ + Deployed: true, + GlobalFlags: flags.GlobalFlags{ + Namespace: "test", + KubeContext: "staging", + }, + }, + } response := Response{ Releases: []Release{ { @@ -66,7 +122,7 @@ func (s *ListTestSuite) TestShouldReturnReleasesWhenSuccessfulAPICall() { }, }, } - s.mockService.On("List", mock.Anything, mock.AnythingOfType("Request")).Return(response, nil) + s.mockService.On("List", mock.Anything, expectedRequestStruct).Return(response, nil) res, err := http.DefaultClient.Do(req) assert.Equal(s.T(), 200, res.StatusCode) @@ -85,9 +141,30 @@ func (s *ListTestSuite) TestShouldReturnReleasesWhenSuccessfulAPICall() { s.mockService.AssertExpectations(s.T()) } +func (s *ListTestSuite) TestShouldReturnNoContentWhenNoReleasesAreAvailable() { + req, _ := http.NewRequest("GET", fmt.Sprintf("%s/clusters/staging/namespaces/test/releases?deployed=true", s.server.URL), nil) + expectedRequestStruct := Request{ + Flags: Flags{ + Deployed: true, + GlobalFlags: flags.GlobalFlags{ + Namespace: "test", + KubeContext: "staging", + }, + }, + } + response := Response{ + Releases: []Release{}, + } + s.mockService.On("List", mock.Anything, expectedRequestStruct).Return(response, nil).Once() + + res, err := http.DefaultClient.Do(req) + assert.Equal(s.T(), 204, res.StatusCode) + require.NoError(s.T(), err) + + s.mockService.AssertExpectations(s.T()) +} func (s *ListTestSuite) TestShouldReturnBadRequestErrorIfItHasInvalidCharacter() { - body := `{"release_status":"unknown""""}` - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/list", s.server.URL), strings.NewReader(body)) + req, _ := http.NewRequest("GET", fmt.Sprintf("%s/clusters/staging/releases?deply=test", s.server.URL), nil) res, err := http.DefaultClient.Do(req) require.NoError(s.T(), err) @@ -96,6 +173,35 @@ func (s *ListTestSuite) TestShouldReturnBadRequestErrorIfItHasInvalidCharacter() require.NoError(s.T(), err) } +func (s *ListTestSuite) TestShouldReturnInternalServerErrorIfListServiceReturnsError() { + req, _ := http.NewRequest("GET", fmt.Sprintf("%s/clusters/staging/namespaces/test/releases?deployed=true", s.server.URL), nil) + expectedRequestStruct := Request{ + Flags: Flags{ + Deployed: true, + GlobalFlags: flags.GlobalFlags{ + Namespace: "test", + KubeContext: "staging", + }, + }, + } + response := Response{} + errorMsg := "test error" + listError := errors.New(errorMsg) + s.mockService.On("List", mock.Anything, expectedRequestStruct).Return(response, listError).Once() + + res, err := http.DefaultClient.Do(req) + assert.Equal(s.T(), 500, res.StatusCode) + require.NoError(s.T(), err) + var actualResponse Response + err = json.NewDecoder(res.Body).Decode(&actualResponse) + require.NoError(s.T(), err) + expectedResponse := Response{ + Error: errorMsg, + } + assert.Equal(s.T(), expectedResponse.Error, actualResponse.Error) + s.mockService.AssertExpectations(s.T()) +} + func (s *ListTestSuite) TearDownTest() { s.server.Close() } diff --git a/api/list/service.go b/api/list/service.go index 9edb500..60d07af 100644 --- a/api/list/service.go +++ b/api/list/service.go @@ -16,7 +16,13 @@ type Service struct { func (s Service) List(ctx context.Context, req Request) (Response, error) { listflags := flags.ListFlags{ - GlobalFlags: req.Flags.GlobalFlags, + GlobalFlags: req.Flags.GlobalFlags, + AllNamespaces: req.AllNamespaces, + Deployed: req.Deployed, + Failed: req.Failed, + Uninstalled: req.Uninstalled, + Uninstalling: req.Uninstalling, + Pending: req.Pending, } lcli, err := s.cli.NewLister(listflags) if err != nil { diff --git a/api/list/service_test.go b/api/list/service_test.go index 44ab4b8..bd9403b 100644 --- a/api/list/service_test.go +++ b/api/list/service_test.go @@ -55,8 +55,18 @@ func TestShouldReturnValidResponseOnSuccess(t *testing.T) { lic := new(mockLister) service := NewService(cli) ctx := context.Background() - req := Request{Flags: Flags{Deployed: true}} - cli.On("NewLister", mock.AnythingOfType("flags.ListFlags")).Return(lic, nil) + req := Request{Flags: Flags{Deployed: true, Uninstalled: true, Pending: true, GlobalFlags: flags.GlobalFlags{ + KubeContext: "abc", Namespace: "test", + }}} + listFlags := flags.ListFlags{ + Deployed: true, + Uninstalled: true, + Pending: true, + GlobalFlags: flags.GlobalFlags{ + KubeContext: "abc", Namespace: "test", + }, + } + cli.On("NewLister", listFlags).Return(lic, nil) chartloader, err := loader.Loader("../testdata/albatross") if err != nil { panic("Could not load chart") diff --git a/api/uninstall/uninstall.go b/api/uninstall/uninstall.go index 13305f6..15cf573 100644 --- a/api/uninstall/uninstall.go +++ b/api/uninstall/uninstall.go @@ -11,6 +11,8 @@ import ( "github.com/gojekfarm/albatross/pkg/helmcli/flags" "github.com/gojekfarm/albatross/pkg/logger" + "github.com/gorilla/mux" + "github.com/gorilla/schema" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" @@ -19,30 +21,15 @@ import ( var ( errInvalidReleaseName = errors.New("uninstall: invalid release name") errUnableToDecodeRequest = errors.New("unable to decode the json payload") + decoder = schema.NewDecoder() ) -// Request Uninstall request body -// swagger:model uninstallRequestBody type Request struct { - // required: true - // example: mysql - ReleaseName string `json:"release_name"` - - // required: false - // example: false - DryRun bool `json:"dry_run"` - - // required: false - // example: false - KeepHistory bool `json:"keep_history"` - - // required: false - // example: false - DisableHooks bool `json:"disable_hooks"` - - // required: false - //example: 300 - Timeout int `json:"timeout"` + ReleaseName string `json:"-" schema:"-"` + DryRun bool `json:"dry_run" schema:"dry_run"` + KeepHistory bool `json:"keep_history" schema:"keep_history"` + DisableHooks bool `json:"disable_hooks" schema:"disable_hooks"` + Timeout int `json:"timeout" schema:"timeout"` flags.GlobalFlags } @@ -82,31 +69,76 @@ type service interface { } // Handler handles an uninstall request -// swagger:route DELETE /uninstall uninstallRelease +// swagger:operation DELETE /clusters/{cluster}/namespaces/{namespace}/releases/{release_name} release uninstallOperation // -// Uninstall a helm release as specified in the request // -// consumes: -// - application/json +// --- +// summary: Uninstall a helm release // produces: -// - application/json -// schemes: http +// - application/json +// parameters: +// - name: cluster +// in: path +// required: true +// default: minikube +// type: string +// format: string +// - name: namespace +// in: path +// required: true +// default: default +// type: string +// format: string +// - name: release_name +// in: path +// required: true +// type: string +// format: string +// default: mysql-final +// - name: dry_run +// in: query +// type: boolean +// default: false +// - name: keep_history +// in: query +// type: boolean +// default: false +// - name: disable_hooks +// in: query +// type: boolean +// default: false +// - name: timeout +// in: query +// type: integer +// default: 300 +// schemes: +// - http // responses: -// 200: uninstallResponse -// 400: uninstallResponse -// 500: uninstallResponse +// '200': +// "$ref": "#/responses/uninstallResponse" +// '400': +// schema: +// $ref: "#/definitions/uninstallErrorResponse" +// '404': +// schema: +// $ref: "#/definitions/uninstallErrorResponse" +// '500': +// "$ref": "#/responses/uninstallResponse" func Handler(s service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() var req Request - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + if err := decoder.Decode(&req, r.URL.Query()); err != nil { logger.Errorf("[Uninstall] error decoding request: %v", err) w.WriteHeader(http.StatusBadRequest) respondWithUninstallError(w, "", errUnableToDecodeRequest) return } - + values := mux.Vars(r) + req.ReleaseName = values["release_name"] + req.GlobalFlags.KubeContext = values["cluster"] + req.GlobalFlags.Namespace = values["namespace"] if err := req.valid(); err != nil { logger.Errorf("[Uninstall] error in request parameters: %v", err) w.WriteHeader(http.StatusBadRequest) diff --git a/api/uninstall/uninstall_test.go b/api/uninstall/uninstall_test.go index 609a02c..4406e41 100644 --- a/api/uninstall/uninstall_test.go +++ b/api/uninstall/uninstall_test.go @@ -7,17 +7,18 @@ import ( "fmt" "net/http" "net/http/httptest" - "strings" "testing" + "github.com/gojekfarm/albatross/pkg/helmcli/flags" + "github.com/gojekfarm/albatross/pkg/logger" + + "github.com/gorilla/mux" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "gotest.tools/assert" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" - - "github.com/gojekfarm/albatross/pkg/logger" ) type mockService struct { @@ -43,16 +44,20 @@ func (s *UninstallTestSuite) SetupSuite() { func (s *UninstallTestSuite) SetupTest() { s.recorder = httptest.NewRecorder() s.mockService = new(mockService) - handler := Handler(s.mockService) - s.server = httptest.NewServer(handler) + router := mux.NewRouter() + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases/{release_name}", Handler(s.mockService)).Methods(http.MethodDelete) + s.server = httptest.NewServer(router) } func (s *UninstallTestSuite) TestShouldReturnReleasesWhenSuccessfulAPICall() { - body := fmt.Sprintf(`{"release_name":"%v", "timeout":2}`, testReleaseName) - req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/uninstall", s.server.URL), strings.NewReader(body)) + req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/clusters/minikube/namespaces/default/releases/%s?timeout=2", s.server.URL, testReleaseName), nil) requestSturct := Request{ ReleaseName: testReleaseName, Timeout: 2, + GlobalFlags: flags.GlobalFlags{ + KubeContext: "minikube", + Namespace: "default", + }, } releaseOptions := &release.MockReleaseOptions{ Name: testReleaseName, @@ -85,10 +90,13 @@ func (s *UninstallTestSuite) TestShouldReturnReleasesWhenSuccessfulAPICall() { func (s *UninstallTestSuite) TestShouldReturnNotFoundErrorIfItHasUnavailableReleaseName() { unavailableReleaseName := "unknown_release" - body := fmt.Sprintf(`{"release_name":"%v"}`, unavailableReleaseName) - req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/uninstall", s.server.URL), strings.NewReader(body)) + req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/clusters/minikube/namespaces/default/releases/%s", s.server.URL, unavailableReleaseName), nil) requestStruct := Request{ ReleaseName: unavailableReleaseName, + GlobalFlags: flags.GlobalFlags{ + KubeContext: "minikube", + Namespace: "default", + }, } s.mockService.On("Uninstall", mock.Anything, requestStruct).Times(1).Return(Response{}, driver.ErrReleaseNotFound) @@ -101,11 +109,14 @@ func (s *UninstallTestSuite) TestShouldReturnNotFoundErrorIfItHasUnavailableRele } func (s *UninstallTestSuite) TestShouldReturnInternalServerErrorIfUninstallThrowsUnknownError() { - body := fmt.Sprintf(`{"release_name":"%v"}`, testReleaseName) errMsg := "Test error Message" - req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/uninstall", s.server.URL), strings.NewReader(body)) + req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/clusters/minikube/namespaces/default/releases/%s", s.server.URL, testReleaseName), nil) requestSturct := Request{ ReleaseName: testReleaseName, + GlobalFlags: flags.GlobalFlags{ + KubeContext: "minikube", + Namespace: "default", + }, } releaseOptions := &release.MockReleaseOptions{ Name: testReleaseName, @@ -133,8 +144,8 @@ func (s *UninstallTestSuite) TestShouldReturnInternalServerErrorIfUninstallThrow } func (s *UninstallTestSuite) TestShouldReturnBadRequestErrorIfItHasInvalidReleaseName() { - body := `{"release_name":""}` - req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/uninstall", s.server.URL), strings.NewReader(body)) + invalidReleaseName := "a very long release name which is invalid plus some more gibberish" + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/clusters/minikube/namespaces/default/releases/%s", s.server.URL, invalidReleaseName), nil) res, err := http.DefaultClient.Do(req) @@ -149,6 +160,50 @@ func (s *UninstallTestSuite) TestShouldReturnBadRequestErrorIfItHasInvalidReleas s.mockService.AssertExpectations(s.T()) } +func (s *UninstallTestSuite) TestAllQueryParam() { + req, _ := http.NewRequest(http.MethodDelete, + fmt.Sprintf("%s/clusters/minikube/namespaces/default/releases/%s?timeout=2&dry_run=true&disable_hooks=true&keep_history=true", s.server.URL, testReleaseName), + nil) + requestSturct := Request{ + ReleaseName: testReleaseName, + Timeout: 2, + DryRun: true, + DisableHooks: true, + KeepHistory: true, + GlobalFlags: flags.GlobalFlags{ + KubeContext: "minikube", + Namespace: "default", + }, + } + releaseOptions := &release.MockReleaseOptions{ + Name: testReleaseName, + Version: 1, + Namespace: "default", + Chart: nil, + Status: release.StatusDeployed, + } + mockRelease := releaseInfo(release.Mock(releaseOptions)) + response := Response{ + Release: mockRelease, + } + s.mockService.On("Uninstall", mock.Anything, requestSturct).Times(1).Return(response, nil) + + res, err := http.DefaultClient.Do(req) + + assert.Equal(s.T(), 200, res.StatusCode) + require.NoError(s.T(), err) + + var actualResponse Response + err = json.NewDecoder(res.Body).Decode(&actualResponse) + assert.NilError(s.T(), err) + assert.Equal(s.T(), mockRelease.Name, actualResponse.Release.Name) + assert.Equal(s.T(), mockRelease.Version, actualResponse.Release.Version) + assert.Equal(s.T(), mockRelease.Namespace, actualResponse.Release.Namespace) + assert.Equal(s.T(), mockRelease.Status, actualResponse.Release.Status) + require.NoError(s.T(), err) + s.mockService.AssertExpectations(s.T()) +} + func (s *UninstallTestSuite) TearDownTest() { s.server.Close() } diff --git a/api/upgrade/upgrade.go b/api/upgrade/upgrade.go index 3514c48..d20ccac 100644 --- a/api/upgrade/upgrade.go +++ b/api/upgrade/upgrade.go @@ -11,18 +11,20 @@ import ( "github.com/gojekfarm/albatross/pkg/helmcli/flags" "github.com/gojekfarm/albatross/pkg/logger" + + "github.com/gorilla/mux" ) // Request is the body for upgrading a release // swagger:model upgradeRequestBody type Request struct { - // example: mysql - Name string `json:"name"` + Name string `json:"-"` // example: stable/mysql Chart string `json:"chart"` // example: {"replicaCount": 1} Values map[string]interface{} `json:"values"` - // example: {"kube_context": "minikube", "namespace":"default"} + // Deprecated field + // example: {"cluster": "minikube", "namespace":"default"} Flags Flags `json:"flags"` } @@ -72,19 +74,48 @@ type service interface { } // Handler handles an upgrade request -// swagger:route POST /upgrade upgradeRelease +// swagger:operation POST /clusters/{cluster}/namespaces/{namespace}/releases/{release_name} release upgradeOperation // -// Upgrades a helm release as specified in the request // +// --- +// summary: Upgrade a helm release deployed at the specified cluster and namespace // consumes: -// - application/json +// - application/json // produces: -// - application/json -// schemes: http +// - application/json +// parameters: +// - name: cluster +// in: path +// required: true +// default: minikube +// type: string +// format: string +// - name: namespace +// in: path +// required: true +// default: default +// type: string +// format: string +// - name: release_name +// in: path +// required: true +// type: string +// format: string +// default: mysql-final +// - name: Body +// in: body +// required: true +// schema: +// "$ref": "#/definitions/upgradeRequestBody" +// schemes: +// - http // responses: -// 200: upgradeResponse -// 400: upgradeResponse -// 500: upgradeResponse +// '200': +// "$ref": "#/responses/upgradeResponse" +// '400': +// description: "Invalid request" +// '500': +// "$ref": "#/responses/upgradeResponse" func Handler(service service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() @@ -95,8 +126,10 @@ func Handler(service service) http.Handler { logger.Errorf("[Upgrade] error decoding request: %v", err) return } - defer r.Body.Close() - + values := mux.Vars(r) + req.Flags.KubeContext = values["cluster"] + req.Flags.Namespace = values["namespace"] + req.Name = values["release_name"] resp, err := service.Upgrade(r.Context(), req) if err != nil { respondUpgradeError(w, "error while upgrading release: %v", err) diff --git a/api/upgrade/upgrade_api_test.go b/api/upgrade/upgrade_api_test.go index dd6ebac..84e78b1 100644 --- a/api/upgrade/upgrade_api_test.go +++ b/api/upgrade/upgrade_api_test.go @@ -9,11 +9,13 @@ import ( "strings" "testing" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "gotest.tools/assert" + "github.com/gojekfarm/albatross/pkg/helmcli/flags" "github.com/gojekfarm/albatross/pkg/logger" "helm.sh/helm/v3/pkg/release" @@ -42,28 +44,40 @@ func (s *UpgradeTestSuite) SetupSuite() { func (s *UpgradeTestSuite) SetupTest() { s.recorder = httptest.NewRecorder() s.mockService = new(mockService) - handler := Handler(s.mockService) - s.server = httptest.NewServer(handler) + router := mux.NewRouter() + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases/{release_name}", Handler(s.mockService)).Methods(http.MethodPost) + s.server = httptest.NewServer(router) } func (s *UpgradeTestSuite) TestShouldReturnDeployedStatusOnSuccessfulUpgrade() { chartName := "stable/redis-ha" body := fmt.Sprintf(`{ "chart":"%s", - "name": "redis-v5", "flags": { - "install": false, - "namespace": "something" + "install": true }, "values": { "usePassword": false }}`, chartName) - - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/upgrade", s.server.URL), strings.NewReader(body)) + req, _ := http.NewRequest("POST", fmt.Sprintf("%s/clusters/staging/namespaces/something/releases/redis-v5", s.server.URL), strings.NewReader(body)) + requestStruct := Request{ + Name: "redis-v5", + Chart: chartName, + Flags: Flags{ + Install: true, + GlobalFlags: flags.GlobalFlags{ + Namespace: "something", + KubeContext: "staging", + }, + }, + Values: map[string]interface{}{ + "usePassword": false, + }, + } response := Response{ Status: release.StatusDeployed.String(), } - s.mockService.On("Upgrade", mock.Anything, mock.AnythingOfType("Request")).Return(response, nil) + s.mockService.On("Upgrade", mock.Anything, requestStruct).Return(response, nil) resp, err := http.DefaultClient.Do(req) @@ -76,12 +90,23 @@ func (s *UpgradeTestSuite) TestShouldReturnInternalServerErrorOnFailure() { chartName := "stable/redis-ha" body := fmt.Sprintf(`{ "chart":"%s", - "name": "redis-v5", "flags": { - "install": true, "namespace": "something", "version": "7.5.4" + "install": true, "namespace": "something2", "version": "7.5.4" }}`, chartName) - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) - s.mockService.On("Upgrade", mock.Anything, mock.AnythingOfType("Request")).Return(Response{}, errors.New("invalid chart")) + req, _ := http.NewRequest("POST", fmt.Sprintf("%s/clusters/staging-context/namespaces/something/releases/redis-v5", s.server.URL), strings.NewReader(body)) + requestStruct := Request{ + Name: "redis-v5", + Chart: chartName, + Flags: Flags{ + Install: true, + Version: "7.5.4", + GlobalFlags: flags.GlobalFlags{ + Namespace: "something", + KubeContext: "staging-context", + }, + }, + } + s.mockService.On("Upgrade", mock.Anything, requestStruct).Return(Response{}, errors.New("invalid chart")) resp, err := http.DefaultClient.Do(req) @@ -97,8 +122,7 @@ func (s *UpgradeTestSuite) TestShouldBadRequestOnInvalidRequest() { "flags": { "install": true, "namespace": true, "version": 7.5.4 }}`, chartName) - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) - s.mockService.On("Upgrade", mock.Anything, mock.AnythingOfType("Request")).Return(Response{}, nil) + req, _ := http.NewRequest("POST", fmt.Sprintf("%s/clusters/staging-context/namespaces/something/releases/redis-v5", s.server.URL), strings.NewReader(body)) resp, err := http.DefaultClient.Do(req) diff --git a/cmd/albatross/albatross.go b/cmd/albatross/albatross.go index 3ea2146..6a2f33a 100644 --- a/cmd/albatross/albatross.go +++ b/cmd/albatross/albatross.go @@ -42,10 +42,11 @@ func startServer() { uninstallHandler := uninstall.Handler(uninstall.NewService(cli)) router.Handle("/ping", ContentTypeMiddle(api.Ping())).Methods(http.MethodGet) - router.Handle("/list", ContentTypeMiddle(listHandler)).Methods(http.MethodGet) - router.Handle("/uninstall", ContentTypeMiddle(uninstallHandler)).Methods(http.MethodDelete) - router.Handle("/install", ContentTypeMiddle(installHandler)).Methods(http.MethodPut) - router.Handle("/upgrade", ContentTypeMiddle(upgradeHandler)).Methods(http.MethodPost) + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases/{release_name}", ContentTypeMiddle(uninstallHandler)).Methods(http.MethodDelete) + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases/{release_name}", ContentTypeMiddle(installHandler)).Methods(http.MethodPut) + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases/{release_name}", ContentTypeMiddle(upgradeHandler)).Methods(http.MethodPost) + router.Handle("/clusters/{cluster}/releases", ContentTypeMiddle(listHandler)).Methods(http.MethodGet) + router.Handle("/clusters/{cluster}/namespaces/{namespace}/releases", ContentTypeMiddle(listHandler)).Methods(http.MethodGet) serveDocumentation(router) err := http.ListenAndServe(fmt.Sprintf(":%d", 8080), router) diff --git a/docs/swagger.json b/docs/swagger.json index de22250..4ac6044 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -16,9 +16,88 @@ }, "host": "localhost:8080", "paths": { - "/install": { + "/clusters/{cluster}/namespaces/{namespace}/releases": { + "get": { + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "tags": [ + "release" + ], + "summary": "List the helm releases for the cluster and namespace", + "operationId": "listOperationWithNamespace", + "parameters": [ + { + "type": "string", + "format": "string", + "default": "minikube", + "name": "cluster", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "default": "default", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "boolean", + "default": false, + "name": "deployed", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "uninstalled", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "failed", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "pending", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "uninstalling", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/listResponse" + }, + "204": { + "description": "No releases found" + }, + "400": { + "$ref": "#/responses/listResponse" + }, + "404": { + "$ref": "#/responses/listResponse" + }, + "500": { + "$ref": "#/responses/listResponse" + } + } + } + }, + "/clusters/{cluster}/namespaces/{namespace}/releases/{release_name}": { "put": { - "description": "Installs a helm release as specified in the request", "consumes": [ "application/json" ], @@ -28,11 +107,40 @@ "schemes": [ "http" ], - "operationId": "installRelease", + "tags": [ + "release" + ], + "summary": "Install helm release at the specified cluster and namespace", + "operationId": "installOperation", "parameters": [ + { + "type": "string", + "format": "string", + "default": "minikube", + "name": "cluster", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "default": "default", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "default": "mysql-final", + "name": "release_name", + "in": "path", + "required": true + }, { "name": "Body", "in": "body", + "required": true, "schema": { "$ref": "#/definitions/installRequestBody" } @@ -43,17 +151,20 @@ "$ref": "#/responses/installResponse" }, "400": { - "$ref": "#/responses/installResponse" + "description": "Invalid request" + }, + "409": { + "description": "", + "schema": { + "$ref": "#/definitions/installResponseErrorBody" + } }, "500": { "$ref": "#/responses/installResponse" } } - } - }, - "/list": { - "get": { - "description": "List helm releases as specified in the request", + }, + "post": { "consumes": [ "application/json" ], @@ -63,49 +174,117 @@ "schemes": [ "http" ], - "operationId": "listRelease", + "tags": [ + "release" + ], + "summary": "Upgrade a helm release deployed at the specified cluster and namespace", + "operationId": "upgradeOperation", "parameters": [ + { + "type": "string", + "format": "string", + "default": "minikube", + "name": "cluster", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "default": "default", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "default": "mysql-final", + "name": "release_name", + "in": "path", + "required": true + }, { "name": "Body", "in": "body", + "required": true, "schema": { - "$ref": "#/definitions/listRequestBody" + "$ref": "#/definitions/upgradeRequestBody" } } ], "responses": { "200": { - "$ref": "#/responses/listResponse" + "$ref": "#/responses/upgradeResponse" }, "400": { - "$ref": "#/responses/listResponse" + "description": "Invalid request" }, "500": { - "$ref": "#/responses/listResponse" + "$ref": "#/responses/upgradeResponse" } } - } - }, - "/uninstall": { + }, "delete": { - "description": "Uninstall a helm release as specified in the request", - "consumes": [ - "application/json" - ], "produces": [ "application/json" ], "schemes": [ "http" ], - "operationId": "uninstallRelease", + "tags": [ + "release" + ], + "summary": "Uninstall a helm release", + "operationId": "uninstallOperation", "parameters": [ { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/uninstallRequestBody" - } + "type": "string", + "format": "string", + "default": "minikube", + "name": "cluster", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "default": "default", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "default": "mysql-final", + "name": "release_name", + "in": "path", + "required": true + }, + { + "type": "boolean", + "default": false, + "name": "dry_run", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "keep_history", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "disable_hooks", + "in": "query" + }, + { + "type": "integer", + "default": 300, + "name": "timeout", + "in": "query" } ], "responses": { @@ -113,7 +292,16 @@ "$ref": "#/responses/uninstallResponse" }, "400": { - "$ref": "#/responses/uninstallResponse" + "description": "", + "schema": { + "$ref": "#/definitions/uninstallErrorResponse" + } + }, + "404": { + "description": "", + "schema": { + "$ref": "#/definitions/uninstallErrorResponse" + } }, "500": { "$ref": "#/responses/uninstallResponse" @@ -121,43 +309,115 @@ } } }, - "/upgrade": { - "post": { - "description": "Upgrades a helm release as specified in the request", - "consumes": [ - "application/json" - ], + "/clusters/{cluster}/releases": { + "get": { "produces": [ "application/json" ], "schemes": [ "http" ], - "operationId": "upgradeRelease", + "tags": [ + "release" + ], + "summary": "List the helm releases for the cluster", + "operationId": "listOperation", "parameters": [ { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/upgradeRequestBody" - } + "type": "string", + "format": "string", + "default": "minikube", + "name": "cluster", + "in": "path", + "required": true + }, + { + "type": "boolean", + "default": false, + "name": "deployed", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "uninstalled", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "failed", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "pending", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "name": "uninstalling", + "in": "query" } ], "responses": { "200": { - "$ref": "#/responses/upgradeResponse" + "$ref": "#/responses/listResponse" + }, + "204": { + "description": "No releases found" }, "400": { - "$ref": "#/responses/upgradeResponse" + "description": "", + "schema": { + "$ref": "#/definitions/listErrorResponse" + } }, "500": { - "$ref": "#/responses/upgradeResponse" + "description": "", + "schema": { + "$ref": "#/definitions/listErrorResponse" + } } } } } }, "definitions": { + "Request": { + "type": "object", + "properties": { + "AllNamespaces": { + "type": "boolean" + }, + "Deployed": { + "type": "boolean" + }, + "Failed": { + "type": "boolean" + }, + "Pending": { + "type": "boolean" + }, + "Uninstalled": { + "type": "boolean" + }, + "Uninstalling": { + "type": "boolean" + }, + "kube_apiserver": { + "type": "string", + "x-go-name": "KubeAPIServer" + }, + "kube_token": { + "type": "string", + "x-go-name": "KubeToken" + } + }, + "x-go-package": "github.com/gojekfarm/albatross/api/list" + }, "Response": { "type": "object", "title": "Response represents the api response for upgrade request.", @@ -182,27 +442,14 @@ "globalFlags": { "description": "GlobalFlags flags which give context about kubernetes cluster to connect to", "type": "object", - "required": [ - "namespace" - ], "properties": { "kube_apiserver": { "type": "string", "x-go-name": "KubeAPIServer" }, - "kube_context": { - "type": "string", - "x-go-name": "KubeContext", - "example": "minikube" - }, "kube_token": { "type": "string", "x-go-name": "KubeToken" - }, - "namespace": { - "type": "string", - "x-go-name": "Namespace", - "example": "default" } }, "x-go-name": "GlobalFlags", @@ -211,9 +458,6 @@ "installFlags": { "description": "Flags additional flags for installing a release", "type": "object", - "required": [ - "namespace" - ], "properties": { "dry_run": { "type": "boolean", @@ -224,20 +468,10 @@ "type": "string", "x-go-name": "KubeAPIServer" }, - "kube_context": { - "type": "string", - "x-go-name": "KubeContext", - "example": "minikube" - }, "kube_token": { "type": "string", "x-go-name": "KubeToken" }, - "namespace": { - "type": "string", - "x-go-name": "Namespace", - "example": "default" - }, "version": { "type": "string", "x-go-name": "Version", @@ -301,11 +535,6 @@ "flags": { "$ref": "#/definitions/installFlags" }, - "name": { - "type": "string", - "x-go-name": "Name", - "example": "mysql" - }, "values": { "type": "object", "additionalProperties": { @@ -342,6 +571,29 @@ "x-go-name": "Response", "x-go-package": "github.com/gojekfarm/albatross/api/install" }, + "installResponseErrorBody": { + "description": "InstallErrorResponse body of install response", + "type": "object", + "properties": { + "error": { + "type": "string", + "x-go-name": "Error" + } + }, + "x-go-name": "InstallErrorResponse", + "x-go-package": "github.com/gojekfarm/albatross/swagger" + }, + "listErrorResponse": { + "description": "ListErrorResponse stub for swagger route for List", + "type": "object", + "properties": { + "Body": { + "$ref": "#/definitions/listReponseBody" + } + }, + "x-go-name": "ListErrorResponse", + "x-go-package": "github.com/gojekfarm/albatross/swagger" + }, "listRelease": { "description": "Release wraps a helm release", "type": "object", @@ -404,123 +656,17 @@ "x-go-name": "Response", "x-go-package": "github.com/gojekfarm/albatross/api/list" }, - "listRequestBody": { - "description": "Request is body of List Route", - "type": "object", - "required": [ - "namespace" - ], - "properties": { - "all-namespaces": { - "type": "boolean", - "x-go-name": "AllNamespaces", - "example": false - }, - "deployed": { - "type": "boolean", - "x-go-name": "Deployed", - "example": false - }, - "failed": { - "type": "boolean", - "x-go-name": "Failed", - "example": false - }, - "kube_apiserver": { - "type": "string", - "x-go-name": "KubeAPIServer" - }, - "kube_context": { - "type": "string", - "x-go-name": "KubeContext", - "example": "minikube" - }, - "kube_token": { - "type": "string", - "x-go-name": "KubeToken" - }, - "namespace": { - "type": "string", - "x-go-name": "Namespace", - "example": "default" - }, - "pending": { - "type": "boolean", - "x-go-name": "Pending", - "example": false - }, - "uninstalled": { - "type": "boolean", - "x-go-name": "Uninstalled", - "example": false - }, - "uninstalling": { - "type": "boolean", - "x-go-name": "Uninstalling", - "example": false - } - }, - "x-go-name": "Request", - "x-go-package": "github.com/gojekfarm/albatross/api/list" - }, - "listRequestFlags": { - "description": "Flags contains all the params supported", + "uninstallErrorResponse": { + "description": "UninstallErrorResponse error body for uninstall action", "type": "object", - "required": [ - "namespace" - ], "properties": { - "all-namespaces": { - "type": "boolean", - "x-go-name": "AllNamespaces", - "example": false - }, - "deployed": { - "type": "boolean", - "x-go-name": "Deployed", - "example": false - }, - "failed": { - "type": "boolean", - "x-go-name": "Failed", - "example": false - }, - "kube_apiserver": { - "type": "string", - "x-go-name": "KubeAPIServer" - }, - "kube_context": { - "type": "string", - "x-go-name": "KubeContext", - "example": "minikube" - }, - "kube_token": { - "type": "string", - "x-go-name": "KubeToken" - }, - "namespace": { + "error": { "type": "string", - "x-go-name": "Namespace", - "example": "default" - }, - "pending": { - "type": "boolean", - "x-go-name": "Pending", - "example": false - }, - "uninstalled": { - "type": "boolean", - "x-go-name": "Uninstalled", - "example": false - }, - "uninstalling": { - "type": "boolean", - "x-go-name": "Uninstalling", - "example": false + "x-go-name": "Error" } }, - "x-go-name": "Flags", - "x-go-package": "github.com/gojekfarm/albatross/api/list" + "x-go-name": "UninstallErrorResponse", + "x-go-package": "github.com/gojekfarm/albatross/swagger" }, "uninstallRelease": { "description": "Release contains metadata about a helm release object", @@ -564,62 +710,6 @@ "x-go-name": "Release", "x-go-package": "github.com/gojekfarm/albatross/api/uninstall" }, - "uninstallRequestBody": { - "description": "Request Uninstall request body", - "type": "object", - "required": [ - "namespace", - "release_name" - ], - "properties": { - "disable_hooks": { - "type": "boolean", - "x-go-name": "DisableHooks", - "example": false - }, - "dry_run": { - "type": "boolean", - "x-go-name": "DryRun", - "example": false - }, - "keep_history": { - "type": "boolean", - "x-go-name": "KeepHistory", - "example": false - }, - "kube_apiserver": { - "type": "string", - "x-go-name": "KubeAPIServer" - }, - "kube_context": { - "type": "string", - "x-go-name": "KubeContext", - "example": "minikube" - }, - "kube_token": { - "type": "string", - "x-go-name": "KubeToken" - }, - "namespace": { - "type": "string", - "x-go-name": "Namespace", - "example": "default" - }, - "release_name": { - "type": "string", - "x-go-name": "ReleaseName", - "example": "mysql" - }, - "timeout": { - "type": "integer", - "format": "int64", - "x-go-name": "Timeout", - "example": 300 - } - }, - "x-go-name": "Request", - "x-go-package": "github.com/gojekfarm/albatross/api/uninstall" - }, "uninstallResponseBody": { "description": "Response is the body of uninstall route", "type": "object", @@ -645,9 +735,6 @@ "upgradeFlags": { "description": "Flags additional flags supported while upgrading a release", "type": "object", - "required": [ - "namespace" - ], "properties": { "dry_run": { "type": "boolean", @@ -663,20 +750,10 @@ "type": "string", "x-go-name": "KubeAPIServer" }, - "kube_context": { - "type": "string", - "x-go-name": "KubeContext", - "example": "minikube" - }, "kube_token": { "type": "string", "x-go-name": "KubeToken" }, - "namespace": { - "type": "string", - "x-go-name": "Namespace", - "example": "default" - }, "version": { "type": "string", "x-go-name": "Version", @@ -740,11 +817,6 @@ "flags": { "$ref": "#/definitions/upgradeFlags" }, - "name": { - "type": "string", - "x-go-name": "Name", - "example": "mysql" - }, "values": { "type": "object", "additionalProperties": { diff --git a/go.mod b/go.mod index 194872e..63a7c60 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/gojekfarm/albatross go 1.13 require ( - github.com/gorilla/mux v1.7.2 + github.com/gorilla/mux v1.8.0 + github.com/gorilla/schema v1.2.0 github.com/stretchr/testify v1.5.1 go.uber.org/zap v1.10.0 gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index 6d6688f..74ae17e 100644 --- a/go.sum +++ b/go.sum @@ -281,6 +281,10 @@ github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1S github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= diff --git a/pkg/helmcli/config/config.go b/pkg/helmcli/config/config.go index fb234fb..7746304 100644 --- a/pkg/helmcli/config/config.go +++ b/pkg/helmcli/config/config.go @@ -57,7 +57,7 @@ func (ac *ActionConfig) setFlags(envconfig *EnvConfig, flg *flags.GlobalFlags) e return ac.Configuration.Init( kubeClientConfig(envconfig, actionNamespace), - actionNamespace, + flg.Namespace, os.Getenv("HELM_DRIVER"), logger.Debugf, ) diff --git a/pkg/helmcli/flags/flags.go b/pkg/helmcli/flags/flags.go index 3fedb2c..df0726b 100644 --- a/pkg/helmcli/flags/flags.go +++ b/pkg/helmcli/flags/flags.go @@ -5,15 +5,14 @@ import "time" // GlobalFlags flags which give context about kubernetes cluster to connect to // swagger:model globalFlags type GlobalFlags struct { - // example: minikube - KubeContext string `json:"kube_context,omitempty"` + KubeContext string `json:"-"` // required: false KubeToken string `json:"kube_token,omitempty"` // required: false KubeAPIServer string `json:"kube_apiserver,omitempty"` // required: true // example: default - Namespace string `json:"namespace"` + Namespace string `json:"-" schema:"namespace"` } type UpgradeFlags struct { diff --git a/swagger/release.go b/swagger/release.go index 5ea4a71..8089ced 100644 --- a/swagger/release.go +++ b/swagger/release.go @@ -14,6 +14,12 @@ type UninstallResponse struct { Body uninstall.Response } +// UninstallErrorResponse error body for uninstall action +// swagger:model uninstallErrorResponse +type UninstallErrorResponse struct { + Error string `json:"error"` +} + // UninstallRequest stub for swagger route for uninstall // swagger:parameters uninstallRelease type UninstallRequest struct { @@ -35,6 +41,13 @@ type ListResponse struct { Body list.Response } +// ListErrorResponse stub for swagger route for List +// swagger:model listErrorResponse +type ListErrorResponse struct { + //in: body + Body list.Response +} + // InstallRequest installing a release // swagger:parameters installRelease type InstallRequest struct { @@ -62,3 +75,9 @@ type UpgradeResponse struct { //in: body Body upgrade.Response } + +// InstallErrorResponse body of install response +// swagger:model installResponseErrorBody +type InstallErrorResponse struct { + Error string `json:"error"` +}