Skip to content

Commit

Permalink
Feature/update routes (#10)
Browse files Browse the repository at this point in the history
API routes to follow REST resource convention
  • Loading branch information
punit-kulal authored Apr 26, 2021
1 parent 2b6cc46 commit b52821e
Show file tree
Hide file tree
Showing 19 changed files with 945 additions and 418 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ dist/
bin/
vendor/
*.swp
cmd/albatross/__debug_bin
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
80 changes: 65 additions & 15 deletions api/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down
51 changes: 37 additions & 14 deletions api/install/install_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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() {
Expand Down
Loading

0 comments on commit b52821e

Please sign in to comment.