Add API endpoint to get repos by kind (#641)

Closes #640

Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
This commit is contained in:
Sergio C. Arteaga 2020-09-09 00:24:15 +02:00 committed by GitHub
parent 8f2122d97c
commit 14beeab31d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 204 additions and 54 deletions

View File

@ -172,6 +172,7 @@ func (h *Handlers) setupRouter() {
r.Route("/repositories", func(r chi.Router) { r.Route("/repositories", func(r chi.Router) {
r.Use(h.Users.RequireLogin) r.Use(h.Users.RequireLogin)
r.Get("/", h.Repositories.GetAll) r.Get("/", h.Repositories.GetAll)
r.Get("/{kind:^helm$|^falco$|^olm$|^opa$}", h.Repositories.GetByKind)
r.Route("/user", func(r chi.Router) { r.Route("/user", func(r chi.Router) {
r.Get("/", h.Repositories.GetOwnedByUser) r.Get("/", h.Repositories.GetOwnedByUser)
r.Post("/", h.Repositories.Add) r.Post("/", h.Repositories.Add)

View File

@ -92,11 +92,29 @@ func (h *Handlers) Delete(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetAll(w http.ResponseWriter, r *http.Request) { func (h *Handlers) GetAll(w http.ResponseWriter, r *http.Request) {
dataJSON, err := h.repoManager.GetAllJSON(r.Context()) dataJSON, err := h.repoManager.GetAllJSON(r.Context())
if err != nil { if err != nil {
h.logger.Error().Err(err).Str("method", "GetAllJSON").Send() h.logger.Error().Err(err).Str("method", "GetAll").Send()
helpers.RenderErrorJSON(w, err) helpers.RenderErrorJSON(w, err)
return return
} }
helpers.RenderJSON(w, dataJSON, 0, http.StatusOK) helpers.RenderJSON(w, dataJSON, helpers.DefaultAPICacheMaxAge, http.StatusOK)
}
// GetByKind is an http handler that returns all the repositories available of
// the kind provided.
func (h *Handlers) GetByKind(w http.ResponseWriter, r *http.Request) {
kind, err := hub.GetKindFromName(chi.URLParam(r, "kind"))
if err != nil {
h.logger.Error().Err(err).Str("method", "GetByKind").Msg("invalid kind")
helpers.RenderErrorJSON(w, hub.ErrInvalidInput)
return
}
dataJSON, err := h.repoManager.GetByKindJSON(r.Context(), kind)
if err != nil {
h.logger.Error().Err(err).Str("method", "GetByKind").Send()
helpers.RenderErrorJSON(w, err)
return
}
helpers.RenderJSON(w, dataJSON, helpers.DefaultAPICacheMaxAge, http.StatusOK)
} }
// GetOwnedByOrg is an http handler that returns the repositories owned by the // GetOwnedByOrg is an http handler that returns the repositories owned by the

View File

@ -370,7 +370,7 @@ func TestGetAll(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type")) assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(0), h.Get("Cache-Control")) assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data) assert.Equal(t, []byte("dataJSON"), data)
hw.rm.AssertExpectations(t) hw.rm.AssertExpectations(t)
}) })
@ -391,6 +391,66 @@ func TestGetAll(t *testing.T) {
}) })
} }
func TestGetByKind(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"kind"},
Values: []string{"olm"},
},
}
t.Run("invalid kind provided", func(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
hw := newHandlersWrapper()
hw.h.GetByKind(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
hw.rm.AssertExpectations(t)
})
t.Run("get repositories by kind succeeded", func(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.rm.On("GetByKindJSON", r.Context(), hub.OLM).Return([]byte("dataJSON"), nil)
hw.h.GetByKind(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := ioutil.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.rm.AssertExpectations(t)
})
t.Run("error getting repositories by kind", func(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.rm.On("GetByKindJSON", r.Context(), hub.OLM).Return(nil, tests.ErrFakeDatabaseFailure)
hw.h.GetByKind(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.rm.AssertExpectations(t)
})
}
func TestGetOwnedByOrg(t *testing.T) { func TestGetOwnedByOrg(t *testing.T) {
rctx := &chi.Context{ rctx := &chi.Context{
URLParams: chi.RouteParams{ URLParams: chi.RouteParams{

View File

@ -381,31 +381,39 @@ paths:
schema: schema:
type: array type: array
items: items:
type: object $ref: "#/components/schemas/RepositoryData"
properties:
kind:
$ref: "#/components/schemas/RepositoryKind"
name:
type: string
example: repo1
display_name:
type: string
example: Repo 1
user_alias:
type: string
example: jdoe
organization_name:
type: string
example: org1
organization_display_name:
type: string
example: Organization 1
"401": "401":
$ref: "#/components/responses/UnauthorizedError" $ref: "#/components/responses/UnauthorizedError"
"429": "429":
$ref: "#/components/responses/TooManyRequests" $ref: "#/components/responses/TooManyRequests"
"500": "500":
$ref: "#/components/responses/InternalServerError" $ref: "#/components/responses/InternalServerError"
/repositories/{^helm$|^falco$|^opa$|^olm$}:
get:
tags:
- Repositories
security:
- ApiKeyAuth: []
- CookieAuth: []
summary: Get all available repositories of the provided kind
parameters:
- $ref: "#/components/parameters/RepoKindParam"
responses:
"200":
description: ""
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/RepositoryData"
"401":
$ref: "#/components/responses/UnauthorizedError"
"429":
$ref: "#/components/responses/TooManyRequests"
"500":
$ref: "#/components/responses/InternalServerError"
/repositories/user: /repositories/user:
get: get:
tags: tags:
@ -1975,38 +1983,7 @@ components:
created_at: created_at:
type: integer type: integer
repository: repository:
type: object $ref: "#/components/schemas/RepositoryData"
properties:
repository_id:
type: string
format: uuid
url:
type: string
nullable: false
format: uri
example: "http://repo-url"
kind:
$ref: "#/components/schemas/RepositoryKind"
name:
type: string
nullable: false
example: repo1
display_name:
type: string
example: Repository 1
user_alias:
type: string
example: jdoe
organization_name:
type: string
example: org1
organization_display_name:
type: string
example: Organization 1
verified_publisher:
type: boolean
official:
type: boolean
nullable: false nullable: false
Repository: Repository:
allOf: allOf:
@ -2035,6 +2012,19 @@ components:
* `1` - Falco rules * `1` - Falco rules
* `2` - OPA policies * `2` - OPA policies
* `3` - OLM operators * `3` - OLM operators
RepositoryKindParam:
type: string
enum:
- helm
- opa
- falco
- olm
description: |
Repository kind name:
* `helm` - Helm charts
* `falco` - Falco rules
* `opa` - OPA policies
* `olm` - OLM operators
RepositorySummary: RepositorySummary:
type: object type: object
properties: properties:
@ -2057,6 +2047,39 @@ components:
required: required:
- name - name
- url - url
RepositoryData:
type: object
properties:
repository_id:
type: string
format: uuid
url:
type: string
nullable: false
format: uri
example: "http://repo-url"
kind:
$ref: "#/components/schemas/RepositoryKind"
name:
type: string
nullable: false
example: repo1
display_name:
type: string
example: Repository 1
user_alias:
type: string
example: jdoe
organization_name:
type: string
example: org1
organization_display_name:
type: string
example: Organization 1
verified_publisher:
type: boolean
official:
type: boolean
Organization: Organization:
allOf: allOf:
- $ref: "#/components/schemas/OrganizationSummary" - $ref: "#/components/schemas/OrganizationSummary"
@ -2375,6 +2398,13 @@ components:
example: pkg1 example: pkg1
required: true required: true
description: Package name description: Package name
RepoKindParam:
in: path
name: ^helm$|^falco$|^opa$|^olm$
schema:
$ref: "#/components/schemas/RepositoryKindParam"
required: true
description: Package kind name
RepoNameParam: RepoNameParam:
in: path in: path
name: repoName name: repoName

View File

@ -103,6 +103,7 @@ type RepositoryManager interface {
GetAllJSON(ctx context.Context) ([]byte, error) GetAllJSON(ctx context.Context) ([]byte, error)
GetByID(ctx context.Context, repositorID string) (*Repository, error) GetByID(ctx context.Context, repositorID string) (*Repository, error)
GetByKind(ctx context.Context, kind RepositoryKind) ([]*Repository, error) GetByKind(ctx context.Context, kind RepositoryKind) ([]*Repository, error)
GetByKindJSON(ctx context.Context, kind RepositoryKind) ([]byte, error)
GetByName(ctx context.Context, name string) (*Repository, error) GetByName(ctx context.Context, name string) (*Repository, error)
GetMetadata(mdFile string) (*RepositoryMetadata, error) GetMetadata(mdFile string) (*RepositoryMetadata, error)
GetPackagesDigest(ctx context.Context, repositoryID string) (map[string]string, error) GetPackagesDigest(ctx context.Context, repositoryID string) (map[string]string, error)

View File

@ -256,6 +256,12 @@ func (m *Manager) GetByKind(ctx context.Context, kind hub.RepositoryKind) ([]*hu
return r, err return r, err
} }
// GetByKindJSON returns all available repositories of the provided kind as a
// json array, which is built by the database.
func (m *Manager) GetByKindJSON(ctx context.Context, kind hub.RepositoryKind) ([]byte, error) {
return m.dbQueryJSON(ctx, "select get_repositories_by_kind($1::int)", kind)
}
// GetByName returns the repository identified by the name provided. // GetByName returns the repository identified by the name provided.
func (m *Manager) GetByName(ctx context.Context, name string) (*hub.Repository, error) { func (m *Manager) GetByName(ctx context.Context, name string) (*hub.Repository, error) {
// Validate input // Validate input

View File

@ -719,6 +719,33 @@ func TestGetByKind(t *testing.T) {
db.AssertExpectations(t) db.AssertExpectations(t)
} }
func TestGetByKindJSON(t *testing.T) {
dbQuery := "select get_repositories_by_kind($1::int)"
ctx := context.WithValue(context.Background(), hub.UserIDKey, "userID")
t.Run("database error", func(t *testing.T) {
db := &tests.DBMock{}
db.On("QueryRow", ctx, dbQuery, hub.OLM).Return(nil, tests.ErrFakeDatabaseFailure)
m := NewManager(cfg, db)
dataJSON, err := m.GetByKindJSON(ctx, hub.OLM)
assert.Equal(t, tests.ErrFakeDatabaseFailure, err)
assert.Nil(t, dataJSON)
db.AssertExpectations(t)
})
t.Run("all repositories data returned successfully", func(t *testing.T) {
db := &tests.DBMock{}
db.On("QueryRow", ctx, dbQuery, hub.OLM).Return([]byte("dataJSON"), nil)
m := NewManager(cfg, db)
dataJSON, err := m.GetByKindJSON(ctx, hub.OLM)
assert.NoError(t, err)
assert.Equal(t, []byte("dataJSON"), dataJSON)
db.AssertExpectations(t)
})
}
func TestGetByName(t *testing.T) { func TestGetByName(t *testing.T) {
dbQuery := "select get_repository_by_name($1::text)" dbQuery := "select get_repository_by_name($1::text)"
ctx := context.Background() ctx := context.Background()

View File

@ -65,6 +65,13 @@ func (m *ManagerMock) GetByKind(ctx context.Context, kind hub.RepositoryKind) ([
return data, args.Error(1) return data, args.Error(1)
} }
// GetByKindJSON implements the RepositoryManager interface.
func (m *ManagerMock) GetByKindJSON(ctx context.Context, kind hub.RepositoryKind) ([]byte, error) {
args := m.Called(ctx, kind)
data, _ := args.Get(0).([]byte)
return data, args.Error(1)
}
// GetByName implements the RepositoryManager interface. // GetByName implements the RepositoryManager interface.
func (m *ManagerMock) GetByName(ctx context.Context, name string) (*hub.Repository, error) { func (m *ManagerMock) GetByName(ctx context.Context, name string) (*hub.Repository, error) {
args := m.Called(ctx, name) args := m.Called(ctx, name)