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.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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue