Skip to content
This repository has been archived by the owner on Jul 16, 2021. It is now read-only.

Commit

Permalink
manage repositories from API and UI (#335)
Browse files Browse the repository at this point in the history
* manage repositories from API and UI

- adds API methods for creating and deleting repositories
- adds basic UI view for managing repositories

* make dialogRef public

* clean up styles

* remove unused icons

* add meta for new route

* use single quotes for consistency

* update codecov config
  • Loading branch information
prydonius authored Aug 15, 2017
1 parent ab03fb7 commit 13f7c27
Show file tree
Hide file tree
Showing 31 changed files with 1,442 additions and 16 deletions.
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

0 comments on commit 13f7c27

Please sign in to comment.