Skip to content
This repository was archived by the owner on Jul 16, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
codecov:
branch: master
coverage:
ignore:
- "src/api/swagger/*"
status:
project:
default:
Expand Down
105 changes: 102 additions & 3 deletions src/api/handlers/repos/repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package repos

import (
"net/http"
"net/url"

log "github.com/Sirupsen/logrus"

middleware "github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/kubernetes-helm/monocular/src/api/data"
"github.com/kubernetes-helm/monocular/src/api/data/helpers"
"github.com/kubernetes-helm/monocular/src/api/data/pointerto"
Expand All @@ -16,9 +20,8 @@ import (
func GetRepos(params reposapi.GetAllReposParams) middleware.Responder {
reposCollection, err := data.GetRepos()
if err != nil {
return reposapi.NewGetAllReposDefault(http.StatusInternalServerError).WithPayload(
&models.Error{Code: pointerto.Int64(http.StatusInternalServerError), Message: pointerto.String("Internal server error")},
)
log.Error("unable to get Repos collection: ", err)
return reposapi.NewGetAllReposDefault(http.StatusInternalServerError).WithPayload(internalServerErrorPayload())
}
var repos []*data.Repo
reposCollection.FindAll(&repos)
Expand All @@ -27,3 +30,99 @@ func GetRepos(params reposapi.GetAllReposParams) middleware.Responder {
payload := handlers.DataResourcesBody(resources)
return reposapi.NewGetAllReposOK().WithPayload(payload)
}

// GetRepo returns an enabled repo
func GetRepo(params reposapi.GetRepoParams) middleware.Responder {
repo := data.Repo{}
reposCollection, err := data.GetRepos()
if err != nil {
log.Error("unable to get Repos collection: ", err)
return reposapi.NewGetRepoDefault(http.StatusInternalServerError).WithPayload(internalServerErrorPayload())
}
err = reposCollection.Find(params.RepoName, &repo)
if err != nil {
log.Error("unable to find Repo: ", err)
return reposapi.NewGetRepoDefault(http.StatusNotFound).WithPayload(notFoundPayload())
}

resource := helpers.MakeRepoResource(models.Repo(repo))
payload := handlers.DataResourceBody(resource)
return reposapi.NewGetRepoOK().WithPayload(payload)
}

// CreateRepo adds a repo to the list of enabled repositories to index
func CreateRepo(params reposapi.CreateRepoParams, releasesEnabled bool) middleware.Responder {
if !releasesEnabled {
return errorResponse("Feature not enabled", http.StatusForbidden)
}

reposCollection, err := data.GetRepos()
if err != nil {
log.Error("unable to get Repos collection: ", err)
return reposapi.NewGetRepoDefault(http.StatusInternalServerError).WithPayload(internalServerErrorPayload())
}

// Params validation
format := strfmt.NewFormats()
if err := params.Data.Validate(format); err != nil {
return reposapi.NewCreateRepoDefault(http.StatusBadRequest).WithPayload(
&models.Error{Code: pointerto.Int64(http.StatusBadRequest), Message: pointerto.String(err.Error())})
}
if _, err := url.ParseRequestURI(*params.Data.URL); err != nil {
return reposapi.NewCreateRepoDefault(http.StatusBadRequest).WithPayload(
&models.Error{Code: pointerto.Int64(http.StatusBadRequest), Message: pointerto.String("URL is invalid")})
}

repo := data.Repo(*params.Data)
if err := reposCollection.Save(&repo); err != nil {
log.Error("unable to save Repo: ", err)
return reposapi.NewCreateRepoDefault(http.StatusInternalServerError).WithPayload(
&models.Error{Code: pointerto.Int64(http.StatusInternalServerError), Message: pointerto.String(err.Error())})
}

resource := helpers.MakeRepoResource(models.Repo(repo))
payload := handlers.DataResourceBody(resource)
return reposapi.NewCreateRepoCreated().WithPayload(payload)
}

// DeleteRepo deletes a repo from the list of enabled repositories to index
func DeleteRepo(params reposapi.DeleteRepoParams, releasesEnabled bool) middleware.Responder {
if !releasesEnabled {
return errorResponse("Feature not enabled", http.StatusForbidden)
}

reposCollection, err := data.GetRepos()
if err != nil {
log.Error("unable to get Repos collection: ", err)
return reposapi.NewGetRepoDefault(http.StatusInternalServerError).WithPayload(internalServerErrorPayload())
}

repo := data.Repo{}
found, err := reposCollection.Delete(params.RepoName)
if err != nil {
log.Error("unable to delete Repo: ", err)
return reposapi.NewCreateRepoDefault(http.StatusInternalServerError).WithPayload(
&models.Error{Code: pointerto.Int64(http.StatusInternalServerError), Message: pointerto.String(err.Error())})
}
if !found {
return reposapi.NewGetRepoDefault(http.StatusNotFound).WithPayload(notFoundPayload())
}

resource := helpers.MakeRepoResource(models.Repo(repo))
payload := handlers.DataResourceBody(resource)
return reposapi.NewGetRepoOK().WithPayload(payload)
}

func notFoundPayload() *models.Error {
return &models.Error{Code: pointerto.Int64(http.StatusNotFound), Message: pointerto.String("404 repository not found")}
}

func internalServerErrorPayload() *models.Error {
return &models.Error{Code: pointerto.Int64(http.StatusInternalServerError), Message: pointerto.String("Internal server error")}
}

func errorResponse(message string, errorCode int64) middleware.Responder {
return reposapi.NewGetAllReposDefault(int(errorCode)).WithPayload(
&models.Error{Code: pointerto.Int64(errorCode), Message: &message},
)
}
161 changes: 159 additions & 2 deletions src/api/handlers/repos/repos_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package repos

import (
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"

log "github.com/Sirupsen/logrus"
"github.com/arschles/assert"
"github.com/go-openapi/runtime"
"github.com/kubernetes-helm/monocular/src/api/config"
Expand All @@ -29,7 +30,163 @@ func TestGetAllRepos200(t *testing.T) {
assert.NoErr(t, testutil.ResourceArrayDataFromJSON(w.Body, &httpBody))
config, err := config.GetConfig()
assert.NoErr(t, err)
assert.Equal(t, len(config.Repos), len(httpBody.Data), "Returns the enabled repos")
assert.Equal(t, len(httpBody.Data), len(config.Repos), "Returns the enabled repos")
}

func TestGetRepo200(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
params := reposapi.GetRepoParams{RepoName: "stable"}
resp := GetRepo(params)
assert.NotNil(t, resp, "GetRepo response")
resp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusOK, "expect a 200 response code")
var httpBody models.ResourceData
assert.NoErr(t, testutil.ResourceDataFromJSON(w.Body, &httpBody))
assert.Equal(t, *httpBody.Data.ID, params.RepoName, "returns the stable repo")
}

func TestGetRepo404(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
params := reposapi.GetRepoParams{RepoName: "inexistant"}
errResp := GetRepo(params)
assert.NotNil(t, errResp, "GetRepo response")
errResp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusNotFound, "expect a 404 response code")
var httpBody models.Error
assert.NoErr(t, testutil.ErrorModelFromJSON(w.Body, &httpBody))
testutil.AssertErrBodyData(t, http.StatusNotFound, "repository", httpBody)
}

func TestCreateRepo201(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
testRepo := models.Repo{
Name: pointerto.String("repoName"),
URL: pointerto.String("http://myrepobucket"),
Source: "http://github.com/my-repo",
}
params := reposapi.CreateRepoParams{Data: &testRepo}
resp := CreateRepo(params, true)
assert.NotNil(t, resp, "CreateRepo response")
resp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusCreated, "expect a 201 response code")
var httpBody models.ResourceData
assert.NoErr(t, testutil.ResourceDataFromJSON(w.Body, &httpBody))
assert.Equal(t, *httpBody.Data.ID, *testRepo.Name, "returns the stable repo")
reposCollection, _ := data.GetRepos()
assert.NoErr(t, reposCollection.Find(*testRepo.Name, &data.Repo{}))
}

func TestCreateRepo400(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
testRepo := models.Repo{
Name: pointerto.String("repoName"),
Source: "http://github.com/my-repo",
}
badURL := models.Repo{
Name: testRepo.Name,
URL: pointerto.String("not-a-valid-url"),
Source: testRepo.Source,
}
tests := []struct {
name string
repo models.Repo
errorMsg string
}{
{"no url", testRepo, "URL in body is required"},
{"bad url", badURL, "URL is invalid"},
}

for _, tt := range tests {
params := reposapi.CreateRepoParams{Data: &tt.repo}
resp := CreateRepo(params, true)
assert.NotNil(t, resp, "CreateRepo response")
resp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusBadRequest, "expect a 400 response code")
var httpBody models.Error
assert.NoErr(t, testutil.ErrorModelFromJSON(w.Body, &httpBody))
assert.NotNil(t, httpBody.Message, tt.name+" error response")
assert.Equal(t, *httpBody.Code, int64(http.StatusBadRequest), "response code in HTTP body data")
assert.True(t, strings.Contains(*httpBody.Message, tt.errorMsg), "error message in HTTP body data")
reposCollection, _ := data.GetRepos()
assert.ExistsErr(t, reposCollection.Find(*testRepo.Name, &data.Repo{}), "invalid repo")
}
}

func TestCreateRepo403(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
testRepo := models.Repo{
Name: pointerto.String("repoName"),
URL: pointerto.String("http://myrepobucket"),
Source: "http://github.com/my-repo",
}
params := reposapi.CreateRepoParams{Data: &testRepo}
resp := CreateRepo(params, false)
assert.NotNil(t, resp, "CreateRepo response")
resp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusForbidden, "expect a 403 response code")
var httpBody models.Error
assert.NoErr(t, testutil.ErrorModelFromJSON(w.Body, &httpBody))
assert.Equal(t, *httpBody.Code, int64(http.StatusForbidden), "response code in HTTP body data")
assert.True(t, strings.Contains(*httpBody.Message, "Feature not enabled"), "error message in HTTP body data")
reposCollection, _ := data.GetRepos()
assert.ExistsErr(t, reposCollection.Find(*testRepo.Name, &data.Repo{}), "invalid repo")
}

func TestDeleteRepo200(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
params := reposapi.DeleteRepoParams{RepoName: "stable"}
resp := DeleteRepo(params, true)
assert.NotNil(t, resp, "DeleteRepo response")
resp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusOK, "expect a 200 response code")
var httpBody models.ResourceData
assert.NoErr(t, testutil.ResourceDataFromJSON(w.Body, &httpBody))
assert.Nil(t, httpBody.Data.ID, "deleted repo")
reposCollection, _ := data.GetRepos()
assert.ExistsErr(t, reposCollection.Find("stable", &data.Repo{}), "deleted repo")
}

func TestDeleteRepo403(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
params := reposapi.DeleteRepoParams{RepoName: "stable"}
resp := DeleteRepo(params, false)
assert.NotNil(t, resp, "CreateRepo response")
resp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusForbidden, "expect a 403 response code")
var httpBody models.Error
assert.NoErr(t, testutil.ErrorModelFromJSON(w.Body, &httpBody))
assert.Equal(t, *httpBody.Code, int64(http.StatusForbidden), "response code in HTTP body data")
assert.True(t, strings.Contains(*httpBody.Message, "Feature not enabled"), "error message in HTTP body data")
reposCollection, _ := data.GetRepos()
assert.NoErr(t, reposCollection.Find("stable", &data.Repo{}))
}

func TestDeleteRepo404(t *testing.T) {
setupTestRepoCache()
defer teardownTestRepoCache()
w := httptest.NewRecorder()
params := reposapi.DeleteRepoParams{RepoName: "inexistant"}
resp := DeleteRepo(params, true)
assert.NotNil(t, resp, "DeleteRepo response")
resp.WriteResponse(w, runtime.JSONProducer())
assert.Equal(t, w.Code, http.StatusNotFound, "expect a 404 response code")
var httpBody models.Error
assert.NoErr(t, testutil.ErrorModelFromJSON(w.Body, &httpBody))
testutil.AssertErrBodyData(t, http.StatusNotFound, "repository", httpBody)
}

func setupTestRepoCache() {
Expand Down
62 changes: 62 additions & 0 deletions src/api/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,61 @@ paths:
description: unexpected error
schema:
$ref: "#/definitions/error"
post:
operationId: createRepo
summary: "add a repository to be indexed"
tags:
- repositories
parameters:
- $ref: '#/parameters/createRepoParams'
responses:
201:
description: Repository added
schema:
$ref: "#/definitions/resourceData"
default:
description: unexpected error
schema:
$ref: "#/definitions/error"
/v1/repos/{repoName}:
get:
operationId: getRepo
summary: get a repository enabled in the backend
tags:
- repositories
parameters:
- name: repoName
in: path
type: string
required: true
responses:
200:
description: repo information
schema:
$ref: "#/definitions/resourceData"
default:
description: unexpected error
schema:
$ref: "#/definitions/error"
delete:
operationId: deleteRepo
summary: delete a repository enabled in the backend
tags:
- repositories
parameters:
- name: repoName
in: path
type: string
required: true
responses:
200:
description: repo deleted
schema:
$ref: "#/definitions/resourceData"
default:
description: unexpected error
schema:
$ref: "#/definitions/error"
/v1/releases:
get:
operationId: getAllReleases
Expand Down Expand Up @@ -244,6 +299,13 @@ parameters:
type: string
description: chart name substring pattern match
required: true
createRepoParams:
name: data
description: Repository information
in: body
type: object
schema:
$ref: "#/definitions/repo"
createReleaseParams:
name: data
description: Information related with the new Helm release.
Expand Down
Loading