-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add API for helm repositories * added documentation for update repository info * updated api endpoint remove flag for deprecated repos * update gojektech-incubatror url * added repository package under helmcli for better package hierarchy * removed username and password from request * return repo entry as response during successful add repo action * updated swagger to reflect correct request and response payload * convert NAME placeholder from kebab case to snakecase * fixed add repo service test * added info about force_update param in case of existing repository name * move repo file validation to a different function * fix cyclometric complexity for the function. * update repository.NAME * made successful response of add api simple repository instead of nested fields Updated swagger docs to reflect the same * renamed check function to checkFileUnlock * Bump version to v1.1.0
- Loading branch information
1 parent
60b7ecc
commit e81aced
Showing
17 changed files
with
1,063 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package repository | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"net/http" | ||
|
||
"github.com/gojekfarm/albatross/pkg/logger" | ||
|
||
"github.com/gorilla/mux" | ||
) | ||
|
||
// AddRequest is the body for PUT request to repository | ||
// swagger:model addRepoRequestBody | ||
type AddRequest struct { | ||
Name string `json:"-"` | ||
URL string `json:"url"` | ||
Username string `json:"username"` | ||
Password string `json:"password"` | ||
|
||
// example: false | ||
ForceUpdate bool `json:"force_update"` | ||
|
||
// example: false | ||
InsecureSkipTLSverify bool `json:"skip_tls_verify"` | ||
} | ||
|
||
type addService interface { | ||
Add(context.Context, AddRequest) (Entry, error) | ||
} | ||
|
||
// AddErrorResponse body of non 2xx response | ||
// swagger:model addRepoErrorResponseBody | ||
type AddErrorResponse struct { | ||
Error string `json:"error"` | ||
} | ||
|
||
// Entry contains metadata about a helm repository entry object | ||
// swagger:model addRepoEntry | ||
type Entry struct { | ||
Name string `json:"name"` | ||
URL string `json:"url"` | ||
Username string `json:"username"` | ||
Password string `json:"password"` | ||
} | ||
|
||
const URLNamePlaceholder string = "repository_name" | ||
|
||
// AddHandler handles a repo add/update request | ||
// swagger:operation PUT /repositories/{repository_name} repository addOperation | ||
// | ||
// Add/Update a chart repository to the server. | ||
// The endpoint is idempotent and a repository can be updated by using the force_update parameter to true | ||
// --- | ||
// produces: | ||
// - application/json | ||
// parameters: | ||
// - name: repository_name | ||
// in: path | ||
// required: true | ||
// type: string | ||
// format: string | ||
// - name: Body | ||
// in: body | ||
// required: true | ||
// schema: | ||
// "$ref": "#/definitions/addRepoRequestBody" | ||
// schemes: | ||
// - http | ||
// responses: | ||
// '200': | ||
// description: "The repository was added successfully" | ||
// schema: | ||
// $ref: "#/definitions/addRepoEntry" | ||
// '400': | ||
// description: "Invalid Request" | ||
// schema: | ||
// $ref: "#/definitions/addRepoErrorResponseBody" | ||
// '500': | ||
// description: "Something went with the server" | ||
// schema: | ||
// $ref: "#/definitions/addRepoErrorResponseBody" | ||
func AddHandler(s addService) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
defer r.Body.Close() | ||
|
||
vars := mux.Vars(r) | ||
var req AddRequest | ||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||
logger.Errorf("[RepoAdd] error decoding request: %v", err) | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
if err := req.isValid(); err != nil { | ||
logger.Errorf("[RepoAdd] error validating request %v", err) | ||
respondAddError(w, "error adding repo", err, http.StatusBadRequest) | ||
return | ||
} | ||
|
||
req.Name = vars[URLNamePlaceholder] | ||
|
||
resp, err := s.Add(r.Context(), req) | ||
|
||
if err != nil { | ||
logger.Errorf("[RepoAdd] error adding repo: %v", err) | ||
respondAddError(w, "error adding repo", err, http.StatusInternalServerError) | ||
return | ||
} | ||
if err := json.NewEncoder(w).Encode(&resp); err != nil { | ||
respondAddError(w, "error writing response: %v", err, http.StatusInternalServerError) | ||
return | ||
} | ||
}) | ||
} | ||
|
||
func respondAddError(w http.ResponseWriter, logprefix string, err error, errorCode int) { | ||
response := AddErrorResponse{Error: err.Error()} | ||
w.WriteHeader(errorCode) | ||
if err := json.NewEncoder(w).Encode(&response); err != nil { | ||
logger.Errorf("[AddRepo] %s %v", logprefix, err) | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
} | ||
|
||
func (req AddRequest) isValid() error { | ||
if req.URL == "" { | ||
return errors.New("url cannot be empty") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package repository | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
"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" | ||
) | ||
|
||
type mockService struct { | ||
mock.Mock | ||
} | ||
|
||
func (m *mockService) Add(ctx context.Context, req AddRequest) (Entry, error) { | ||
args := m.Called(ctx, req) | ||
return args.Get(0).(Entry), args.Error(1) | ||
} | ||
|
||
type RepoAddTestSuite struct { | ||
suite.Suite | ||
recorder *httptest.ResponseRecorder | ||
server *httptest.Server | ||
mockService *mockService | ||
} | ||
|
||
func (s *RepoAddTestSuite) SetupSuite() { | ||
logger.Setup("default") | ||
} | ||
|
||
func (s *RepoAddTestSuite) SetupTest() { | ||
s.recorder = httptest.NewRecorder() | ||
s.mockService = new(mockService) | ||
router := mux.NewRouter() | ||
path := fmt.Sprintf("/repositories/{%s}", URLNamePlaceholder) | ||
router.Handle(path, AddHandler(s.mockService)).Methods(http.MethodPut) | ||
s.server = httptest.NewServer(router) | ||
} | ||
|
||
func (s *RepoAddTestSuite) TestRepoAddSuccessFul() { | ||
repoName := "gojek-incubator" | ||
urlName := "https://gojek.github.io/charts/incubator/" | ||
body := fmt.Sprintf(`{"url":"%s", "username":"admin", "password":"123", | ||
"allow_deprecated_repos":true, "force_update": true, "skip_tls_verify": true}`, urlName) | ||
|
||
req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/repositories/%s", s.server.URL, repoName), strings.NewReader(body)) | ||
request := AddRequest{ | ||
Name: repoName, | ||
URL: urlName, | ||
Username: "admin", | ||
Password: "123", | ||
ForceUpdate: true, | ||
InsecureSkipTLSverify: true, | ||
} | ||
|
||
mockAddResponse := Entry{ | ||
Name: request.Name, | ||
URL: request.URL, | ||
Username: request.Username, | ||
Password: request.Password, | ||
} | ||
s.mockService.On("Add", mock.Anything, request).Return(mockAddResponse, nil) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
assert.Equal(s.T(), http.StatusOK, resp.StatusCode) | ||
|
||
respBody, _ := ioutil.ReadAll(resp.Body) | ||
require.NoError(s.T(), err) | ||
|
||
parsedResponse := &Entry{} | ||
err = json.Unmarshal(respBody, parsedResponse) | ||
require.NoError(s.T(), err) | ||
assert.Equal(s.T(), mockAddResponse.Name, parsedResponse.Name) | ||
assert.Equal(s.T(), mockAddResponse.URL, parsedResponse.URL) | ||
assert.Equal(s.T(), mockAddResponse.Username, parsedResponse.Username) | ||
assert.Equal(s.T(), mockAddResponse.Password, parsedResponse.Password) | ||
s.mockService.AssertExpectations(s.T()) | ||
} | ||
|
||
func (s *RepoAddTestSuite) TestRepoAddInvalidRequest() { | ||
repoName := "gojek-incubator" | ||
body := `{"username":"admin", "password":"123"}` | ||
|
||
req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/repositories/%s", s.server.URL, repoName), strings.NewReader(body)) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) | ||
expectedResponse := `{"error":"url cannot be empty"}` + "\n" | ||
respBody, _ := ioutil.ReadAll(resp.Body) | ||
assert.Equal(s.T(), expectedResponse, string(respBody)) | ||
require.NoError(s.T(), err) | ||
s.mockService.AssertExpectations(s.T()) | ||
} | ||
|
||
func (s *RepoAddTestSuite) TestRepoAddFailure() { | ||
repoName := "gojek-incubator" | ||
urlName := "https://gojek.github.io/charts/incubator/" | ||
body := fmt.Sprintf(`{"url":"%s", "username":"admin", "password":"123"}`, urlName) | ||
|
||
req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/repositories/%s", s.server.URL, repoName), strings.NewReader(body)) | ||
request := AddRequest{ | ||
Name: repoName, | ||
URL: urlName, | ||
Username: "admin", | ||
Password: "123", | ||
} | ||
|
||
s.mockService.On("Add", mock.Anything, request).Return(Entry{}, errors.New("error adding repository")) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
assert.Equal(s.T(), http.StatusInternalServerError, resp.StatusCode) | ||
expectedResponse := `{"error":"error adding repository"}` + "\n" | ||
respBody, _ := ioutil.ReadAll(resp.Body) | ||
assert.Equal(s.T(), expectedResponse, string(respBody)) | ||
require.NoError(s.T(), err) | ||
s.mockService.AssertExpectations(s.T()) | ||
} | ||
|
||
func TestRepoAddAPI(t *testing.T) { | ||
suite.Run(t, new(RepoAddTestSuite)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package repository | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/gojekfarm/albatross/pkg/helmcli/flags" | ||
"github.com/gojekfarm/albatross/pkg/helmcli/repository" | ||
"github.com/gojekfarm/albatross/pkg/logger" | ||
|
||
"helm.sh/helm/v3/pkg/repo" | ||
) | ||
|
||
type Service struct { | ||
cli repository.Client | ||
} | ||
|
||
func (s Service) Add(ctx context.Context, req AddRequest) (Entry, error) { | ||
addFlags := flags.AddFlags{ | ||
Name: req.Name, | ||
URL: req.URL, | ||
ForceUpdate: req.ForceUpdate, | ||
} | ||
|
||
adder, err := s.cli.NewAdder(addFlags) | ||
if err != nil { | ||
return Entry{}, err | ||
} | ||
|
||
entry, err := adder.Add(ctx) | ||
if err != nil { | ||
return Entry{}, err | ||
} | ||
return getEntry(entry) | ||
} | ||
|
||
func NewService(cli repository.Client) Service { | ||
return Service{cli} | ||
} | ||
|
||
func getEntry(entry *repo.Entry) (Entry, error) { | ||
if entry != nil { | ||
logger.Infof("Repository %s with URL: %s has been added", entry.Name, entry.URL) | ||
return Entry{ | ||
Name: entry.Name, | ||
URL: entry.URL, | ||
Username: entry.Username, | ||
Password: entry.Password, | ||
}, nil | ||
} | ||
|
||
return Entry{}, fmt.Errorf("couldn't get repository from user") | ||
} |
Oops, something went wrong.