mirror of https://github.com/artifacthub/hub.git
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:
parent
8f2122d97c
commit
14beeab31d
|
|
@ -172,6 +172,7 @@ func (h *Handlers) setupRouter() {
|
|||
r.Route("/repositories", func(r chi.Router) {
|
||||
r.Use(h.Users.RequireLogin)
|
||||
r.Get("/", h.Repositories.GetAll)
|
||||
r.Get("/{kind:^helm$|^falco$|^olm$|^opa$}", h.Repositories.GetByKind)
|
||||
r.Route("/user", func(r chi.Router) {
|
||||
r.Get("/", h.Repositories.GetOwnedByUser)
|
||||
r.Post("/", h.Repositories.Add)
|
||||
|
|
|
|||
|
|
@ -92,11 +92,29 @@ func (h *Handlers) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
func (h *Handlers) GetAll(w http.ResponseWriter, r *http.Request) {
|
||||
dataJSON, err := h.repoManager.GetAllJSON(r.Context())
|
||||
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)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ func TestGetAll(t *testing.T) {
|
|||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
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)
|
||||
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) {
|
||||
rctx := &chi.Context{
|
||||
URLParams: chi.RouteParams{
|
||||
|
|
|
|||
|
|
@ -381,31 +381,39 @@ paths:
|
|||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
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
|
||||
$ref: "#/components/schemas/RepositoryData"
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"429":
|
||||
$ref: "#/components/responses/TooManyRequests"
|
||||
"500":
|
||||
$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:
|
||||
get:
|
||||
tags:
|
||||
|
|
@ -1975,6 +1983,71 @@ components:
|
|||
created_at:
|
||||
type: integer
|
||||
repository:
|
||||
$ref: "#/components/schemas/RepositoryData"
|
||||
nullable: false
|
||||
Repository:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/RepositorySummary"
|
||||
- type: object
|
||||
properties:
|
||||
repository_id:
|
||||
type: string
|
||||
format: uuid
|
||||
last_tracking_ts:
|
||||
type: integer
|
||||
nullable: true
|
||||
last_tracking_errors:
|
||||
type: string
|
||||
example: Error
|
||||
RepositoryKind:
|
||||
type: integer
|
||||
enum:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
description: |
|
||||
Repository kind:
|
||||
* `0` - Helm charts
|
||||
* `1` - Falco rules
|
||||
* `2` - OPA policies
|
||||
* `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:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
nullable: false
|
||||
example: repo1
|
||||
display_name:
|
||||
type: string
|
||||
example: Repository 1
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
nullable: false
|
||||
example: "http://repourl"
|
||||
verified_publisher:
|
||||
type: boolean
|
||||
official:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
- url
|
||||
RepositoryData:
|
||||
type: object
|
||||
properties:
|
||||
repository_id:
|
||||
|
|
@ -2007,56 +2080,6 @@ components:
|
|||
type: boolean
|
||||
official:
|
||||
type: boolean
|
||||
nullable: false
|
||||
Repository:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/RepositorySummary"
|
||||
- type: object
|
||||
properties:
|
||||
repository_id:
|
||||
type: string
|
||||
format: uuid
|
||||
last_tracking_ts:
|
||||
type: integer
|
||||
nullable: true
|
||||
last_tracking_errors:
|
||||
type: string
|
||||
example: Error
|
||||
RepositoryKind:
|
||||
type: integer
|
||||
enum:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
description: |
|
||||
Repository kind:
|
||||
* `0` - Helm charts
|
||||
* `1` - Falco rules
|
||||
* `2` - OPA policies
|
||||
* `3` - OLM operators
|
||||
RepositorySummary:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
nullable: false
|
||||
example: repo1
|
||||
display_name:
|
||||
type: string
|
||||
example: Repository 1
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
nullable: false
|
||||
example: "http://repourl"
|
||||
verified_publisher:
|
||||
type: boolean
|
||||
official:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
- url
|
||||
Organization:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/OrganizationSummary"
|
||||
|
|
@ -2375,6 +2398,13 @@ components:
|
|||
example: pkg1
|
||||
required: true
|
||||
description: Package name
|
||||
RepoKindParam:
|
||||
in: path
|
||||
name: ^helm$|^falco$|^opa$|^olm$
|
||||
schema:
|
||||
$ref: "#/components/schemas/RepositoryKindParam"
|
||||
required: true
|
||||
description: Package kind name
|
||||
RepoNameParam:
|
||||
in: path
|
||||
name: repoName
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ type RepositoryManager interface {
|
|||
GetAllJSON(ctx context.Context) ([]byte, error)
|
||||
GetByID(ctx context.Context, repositorID string) (*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)
|
||||
GetMetadata(mdFile string) (*RepositoryMetadata, error)
|
||||
GetPackagesDigest(ctx context.Context, repositoryID string) (map[string]string, error)
|
||||
|
|
|
|||
|
|
@ -256,6 +256,12 @@ func (m *Manager) GetByKind(ctx context.Context, kind hub.RepositoryKind) ([]*hu
|
|||
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.
|
||||
func (m *Manager) GetByName(ctx context.Context, name string) (*hub.Repository, error) {
|
||||
// Validate input
|
||||
|
|
|
|||
|
|
@ -719,6 +719,33 @@ func TestGetByKind(t *testing.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) {
|
||||
dbQuery := "select get_repository_by_name($1::text)"
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
|
|
@ -65,6 +65,13 @@ func (m *ManagerMock) GetByKind(ctx context.Context, kind hub.RepositoryKind) ([
|
|||
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.
|
||||
func (m *ManagerMock) GetByName(ctx context.Context, name string) (*hub.Repository, error) {
|
||||
args := m.Called(ctx, name)
|
||||
|
|
|
|||
Loading…
Reference in New Issue