Add a rudimentary filter option when listing entities

This change adds the ability to filter the list of entities returned
by the API by entity owner, name or endpoint, depending on the entity
type.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-06-17 22:37:18 +00:00
parent 2fd0e720e6
commit 499fbde60c
32 changed files with 879 additions and 73 deletions

View file

@ -66,13 +66,30 @@ func (a *APIController) CreateEnterpriseHandler(w http.ResponseWriter, r *http.R
//
// List all enterprises.
//
// Parameters:
// + name: name
// description: Exact enterprise name to filter by
// type: string
// in: query
// required: false
//
// + name: endpoint
// description: Exact endpoint name to filter by
// type: string
// in: query
// required: false
//
// Responses:
// 200: Enterprises
// default: APIErrorResponse
func (a *APIController) ListEnterprisesHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
enterprise, err := a.r.ListEnterprises(ctx)
filter := runnerParams.EnterpriseFilter{
Name: r.URL.Query().Get("name"),
Endpoint: r.URL.Query().Get("endpoint"),
}
enterprise, err := a.r.ListEnterprises(ctx, filter)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing enterprise")
handleError(ctx, w, err)

View file

@ -67,13 +67,30 @@ func (a *APIController) CreateOrgHandler(w http.ResponseWriter, r *http.Request)
//
// List organizations.
//
// Parameters:
// + name: name
// description: Exact organization name to filter by
// type: string
// in: query
// required: false
//
// + name: endpoint
// description: Exact endpoint name to filter by
// type: string
// in: query
// required: false
//
// Responses:
// 200: Organizations
// default: APIErrorResponse
func (a *APIController) ListOrgsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
orgs, err := a.r.ListOrganizations(ctx)
filter := runnerParams.OrganizationFilter{
Name: r.URL.Query().Get("name"),
Endpoint: r.URL.Query().Get("endpoint"),
}
orgs, err := a.r.ListOrganizations(ctx, filter)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing orgs")
handleError(ctx, w, err)

View file

@ -67,13 +67,37 @@ func (a *APIController) CreateRepoHandler(w http.ResponseWriter, r *http.Request
//
// List repositories.
//
// Parameters:
// + name: owner
// description: Exact owner name to filter by
// type: string
// in: query
// required: false
//
// + name: name
// description: Exact repository name to filter by
// type: string
// in: query
// required: false
//
// + name: endpoint
// description: Exact endpoint name to filter by
// type: string
// in: query
// required: false
//
// Responses:
// 200: Repositories
// default: APIErrorResponse
func (a *APIController) ListReposHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
repos, err := a.r.ListRepositories(ctx)
filter := runnerParams.RepositoryFilter{
Name: r.URL.Query().Get("name"),
Owner: r.URL.Query().Get("owner"),
Endpoint: r.URL.Query().Get("endpoint"),
}
repos, err := a.r.ListRepositories(ctx, filter)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "listing repositories")
handleError(ctx, w, err)

View file

@ -418,6 +418,15 @@ paths:
/enterprises:
get:
operationId: ListEnterprises
parameters:
- description: Exact enterprise name to filter by
in: query
name: name
type: string
- description: Exact endpoint name to filter by
in: query
name: endpoint
type: string
responses:
"200":
description: Enterprises
@ -1254,6 +1263,15 @@ paths:
/organizations:
get:
operationId: ListOrgs
parameters:
- description: Exact organization name to filter by
in: query
name: name
type: string
- description: Exact endpoint name to filter by
in: query
name: endpoint
type: string
responses:
"200":
description: Organizations
@ -1754,6 +1772,19 @@ paths:
/repositories:
get:
operationId: ListRepos
parameters:
- description: Exact owner name to filter by
in: query
name: owner
type: string
- description: Exact repository name to filter by
in: query
name: name
type: string
- description: Exact endpoint name to filter by
in: query
name: endpoint
type: string
responses:
"200":
description: Repositories

View file

@ -60,6 +60,19 @@ ListEnterprisesParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
*/
type ListEnterprisesParams struct {
/* Endpoint.
Exact endpoint name to filter by
*/
Endpoint *string
/* Name.
Exact enterprise name to filter by
*/
Name *string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
@ -113,6 +126,28 @@ func (o *ListEnterprisesParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithEndpoint adds the endpoint to the list enterprises params
func (o *ListEnterprisesParams) WithEndpoint(endpoint *string) *ListEnterprisesParams {
o.SetEndpoint(endpoint)
return o
}
// SetEndpoint adds the endpoint to the list enterprises params
func (o *ListEnterprisesParams) SetEndpoint(endpoint *string) {
o.Endpoint = endpoint
}
// WithName adds the name to the list enterprises params
func (o *ListEnterprisesParams) WithName(name *string) *ListEnterprisesParams {
o.SetName(name)
return o
}
// SetName adds the name to the list enterprises params
func (o *ListEnterprisesParams) SetName(name *string) {
o.Name = name
}
// WriteToRequest writes these params to a swagger request
func (o *ListEnterprisesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
@ -121,6 +156,40 @@ func (o *ListEnterprisesParams) WriteToRequest(r runtime.ClientRequest, reg strf
}
var res []error
if o.Endpoint != nil {
// query param endpoint
var qrEndpoint string
if o.Endpoint != nil {
qrEndpoint = *o.Endpoint
}
qEndpoint := qrEndpoint
if qEndpoint != "" {
if err := r.SetQueryParam("endpoint", qEndpoint); err != nil {
return err
}
}
}
if o.Name != nil {
// query param name
var qrName string
if o.Name != nil {
qrName = *o.Name
}
qName := qrName
if qName != "" {
if err := r.SetQueryParam("name", qName); err != nil {
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}

View file

@ -60,6 +60,19 @@ ListOrgsParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
*/
type ListOrgsParams struct {
/* Endpoint.
Exact endpoint name to filter by
*/
Endpoint *string
/* Name.
Exact organization name to filter by
*/
Name *string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
@ -113,6 +126,28 @@ func (o *ListOrgsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithEndpoint adds the endpoint to the list orgs params
func (o *ListOrgsParams) WithEndpoint(endpoint *string) *ListOrgsParams {
o.SetEndpoint(endpoint)
return o
}
// SetEndpoint adds the endpoint to the list orgs params
func (o *ListOrgsParams) SetEndpoint(endpoint *string) {
o.Endpoint = endpoint
}
// WithName adds the name to the list orgs params
func (o *ListOrgsParams) WithName(name *string) *ListOrgsParams {
o.SetName(name)
return o
}
// SetName adds the name to the list orgs params
func (o *ListOrgsParams) SetName(name *string) {
o.Name = name
}
// WriteToRequest writes these params to a swagger request
func (o *ListOrgsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
@ -121,6 +156,40 @@ func (o *ListOrgsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Regi
}
var res []error
if o.Endpoint != nil {
// query param endpoint
var qrEndpoint string
if o.Endpoint != nil {
qrEndpoint = *o.Endpoint
}
qEndpoint := qrEndpoint
if qEndpoint != "" {
if err := r.SetQueryParam("endpoint", qEndpoint); err != nil {
return err
}
}
}
if o.Name != nil {
// query param name
var qrName string
if o.Name != nil {
qrName = *o.Name
}
qName := qrName
if qName != "" {
if err := r.SetQueryParam("name", qName); err != nil {
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}

View file

@ -60,6 +60,25 @@ ListReposParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
*/
type ListReposParams struct {
/* Endpoint.
Exact endpoint name to filter by
*/
Endpoint *string
/* Name.
Exact repository name to filter by
*/
Name *string
/* Owner.
Exact owner name to filter by
*/
Owner *string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
@ -113,6 +132,39 @@ func (o *ListReposParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithEndpoint adds the endpoint to the list repos params
func (o *ListReposParams) WithEndpoint(endpoint *string) *ListReposParams {
o.SetEndpoint(endpoint)
return o
}
// SetEndpoint adds the endpoint to the list repos params
func (o *ListReposParams) SetEndpoint(endpoint *string) {
o.Endpoint = endpoint
}
// WithName adds the name to the list repos params
func (o *ListReposParams) WithName(name *string) *ListReposParams {
o.SetName(name)
return o
}
// SetName adds the name to the list repos params
func (o *ListReposParams) SetName(name *string) {
o.Name = name
}
// WithOwner adds the owner to the list repos params
func (o *ListReposParams) WithOwner(owner *string) *ListReposParams {
o.SetOwner(owner)
return o
}
// SetOwner adds the owner to the list repos params
func (o *ListReposParams) SetOwner(owner *string) {
o.Owner = owner
}
// WriteToRequest writes these params to a swagger request
func (o *ListReposParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
@ -121,6 +173,57 @@ func (o *ListReposParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Reg
}
var res []error
if o.Endpoint != nil {
// query param endpoint
var qrEndpoint string
if o.Endpoint != nil {
qrEndpoint = *o.Endpoint
}
qEndpoint := qrEndpoint
if qEndpoint != "" {
if err := r.SetQueryParam("endpoint", qEndpoint); err != nil {
return err
}
}
}
if o.Name != nil {
// query param name
var qrName string
if o.Name != nil {
qrName = *o.Name
}
qName := qrName
if qName != "" {
if err := r.SetQueryParam("name", qName); err != nil {
return err
}
}
}
if o.Owner != nil {
// query param owner
var qrOwner string
if o.Owner != nil {
qrOwner = *o.Owner
}
qOwner := qrOwner
if qOwner != "" {
if err := r.SetQueryParam("owner", qOwner); err != nil {
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}

View file

@ -28,6 +28,7 @@ import (
var (
enterpriseName string
enterpriseEndpoint string
enterpriseWebhookSecret string
enterpriseCreds string
)
@ -85,6 +86,8 @@ var enterpriseListCmd = &cobra.Command{
}
listEnterprisesReq := apiClientEnterprises.NewListEnterprisesParams()
listEnterprisesReq.Name = &enterpriseName
listEnterprisesReq.Endpoint = &enterpriseEndpoint
response, err := apiCli.Enterprises.ListEnterprises(listEnterprisesReq, authToken)
if err != nil {
return err
@ -185,6 +188,8 @@ func init() {
enterpriseAddCmd.Flags().StringVar(&poolBalancerType, "pool-balancer-type", string(params.PoolBalancerTypeRoundRobin), "The balancing strategy to use when creating runners in pools matching requested labels.")
enterpriseListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
enterpriseListCmd.Flags().StringVarP(&enterpriseName, "name", "n", "", "Exact enterprise name to filter by.")
enterpriseListCmd.Flags().StringVarP(&enterpriseEndpoint, "endpoint", "e", "", "Exact endpoint name to filter by.")
enterpriseAddCmd.MarkFlagRequired("credentials") //nolint
enterpriseAddCmd.MarkFlagRequired("name") //nolint

View file

@ -29,6 +29,7 @@ import (
var (
orgName string
orgEndpoint string
orgWebhookSecret string
orgCreds string
orgRandomWebhookSecret bool
@ -243,6 +244,8 @@ var orgListCmd = &cobra.Command{
}
listOrgsReq := apiClientOrgs.NewListOrgsParams()
listOrgsReq.Name = &orgName
listOrgsReq.Endpoint = &orgEndpoint
response, err := apiCli.Organizations.ListOrgs(listOrgsReq, authToken)
if err != nil {
return err
@ -314,7 +317,10 @@ func init() {
orgAddCmd.Flags().BoolVar(&installOrgWebhook, "install-webhook", false, "Install the webhook as part of the add operation.")
orgAddCmd.MarkFlagsMutuallyExclusive("webhook-secret", "random-webhook-secret")
orgAddCmd.MarkFlagsOneRequired("webhook-secret", "random-webhook-secret")
orgListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
orgListCmd.Flags().StringVarP(&orgName, "name", "n", "", "Exact org name to filter by.")
orgListCmd.Flags().StringVarP(&orgEndpoint, "endpoint", "e", "", "Exact endpoint name to filter by.")
orgAddCmd.MarkFlagRequired("credentials") //nolint
orgAddCmd.MarkFlagRequired("name") //nolint

View file

@ -30,6 +30,7 @@ import (
var (
repoOwner string
repoName string
repoEndpoint string
repoWebhookSecret string
repoCreds string
forgeType string
@ -213,6 +214,9 @@ var repoListCmd = &cobra.Command{
}
listReposReq := apiClientRepos.NewListReposParams()
listReposReq.Name = &repoName
listReposReq.Owner = &repoOwner
listReposReq.Endpoint = &repoEndpoint
response, err := apiCli.Repositories.ListRepos(listReposReq, authToken)
if err != nil {
return err
@ -321,6 +325,9 @@ func init() {
repoAddCmd.MarkFlagsOneRequired("webhook-secret", "random-webhook-secret")
repoListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
repoListCmd.Flags().StringVarP(&repoName, "name", "n", "", "Exact repo name to filter by.")
repoListCmd.Flags().StringVarP(&repoOwner, "owner", "o", "", "Exact repo owner to filter by.")
repoListCmd.Flags().StringVarP(&repoEndpoint, "endpoint", "e", "", "Exact endpoint name to filter by.")
repoAddCmd.MarkFlagRequired("credentials") //nolint
repoAddCmd.MarkFlagRequired("owner") //nolint

View file

@ -1524,9 +1524,9 @@ func (_m *Store) ListAllScaleSets(ctx context.Context) ([]params.ScaleSet, error
return r0, r1
}
// ListEnterprises provides a mock function with given fields: ctx
func (_m *Store) ListEnterprises(ctx context.Context) ([]params.Enterprise, error) {
ret := _m.Called(ctx)
// ListEnterprises provides a mock function with given fields: ctx, filter
func (_m *Store) ListEnterprises(ctx context.Context, filter params.EnterpriseFilter) ([]params.Enterprise, error) {
ret := _m.Called(ctx, filter)
if len(ret) == 0 {
panic("no return value specified for ListEnterprises")
@ -1534,19 +1534,19 @@ func (_m *Store) ListEnterprises(ctx context.Context) ([]params.Enterprise, erro
var r0 []params.Enterprise
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]params.Enterprise, error)); ok {
return rf(ctx)
if rf, ok := ret.Get(0).(func(context.Context, params.EnterpriseFilter) ([]params.Enterprise, error)); ok {
return rf(ctx, filter)
}
if rf, ok := ret.Get(0).(func(context.Context) []params.Enterprise); ok {
r0 = rf(ctx)
if rf, ok := ret.Get(0).(func(context.Context, params.EnterpriseFilter) []params.Enterprise); ok {
r0 = rf(ctx, filter)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Enterprise)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
if rf, ok := ret.Get(1).(func(context.Context, params.EnterpriseFilter) error); ok {
r1 = rf(ctx, filter)
} else {
r1 = ret.Error(1)
}
@ -1824,9 +1824,9 @@ func (_m *Store) ListJobsByStatus(ctx context.Context, status params.JobStatus)
return r0, r1
}
// ListOrganizations provides a mock function with given fields: ctx
func (_m *Store) ListOrganizations(ctx context.Context) ([]params.Organization, error) {
ret := _m.Called(ctx)
// ListOrganizations provides a mock function with given fields: ctx, filter
func (_m *Store) ListOrganizations(ctx context.Context, filter params.OrganizationFilter) ([]params.Organization, error) {
ret := _m.Called(ctx, filter)
if len(ret) == 0 {
panic("no return value specified for ListOrganizations")
@ -1834,19 +1834,19 @@ func (_m *Store) ListOrganizations(ctx context.Context) ([]params.Organization,
var r0 []params.Organization
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]params.Organization, error)); ok {
return rf(ctx)
if rf, ok := ret.Get(0).(func(context.Context, params.OrganizationFilter) ([]params.Organization, error)); ok {
return rf(ctx, filter)
}
if rf, ok := ret.Get(0).(func(context.Context) []params.Organization); ok {
r0 = rf(ctx)
if rf, ok := ret.Get(0).(func(context.Context, params.OrganizationFilter) []params.Organization); ok {
r0 = rf(ctx, filter)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Organization)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
if rf, ok := ret.Get(1).(func(context.Context, params.OrganizationFilter) error); ok {
r1 = rf(ctx, filter)
} else {
r1 = ret.Error(1)
}
@ -1884,9 +1884,9 @@ func (_m *Store) ListPoolInstances(ctx context.Context, poolID string) ([]params
return r0, r1
}
// ListRepositories provides a mock function with given fields: ctx
func (_m *Store) ListRepositories(ctx context.Context) ([]params.Repository, error) {
ret := _m.Called(ctx)
// ListRepositories provides a mock function with given fields: ctx, filter
func (_m *Store) ListRepositories(ctx context.Context, filter params.RepositoryFilter) ([]params.Repository, error) {
ret := _m.Called(ctx, filter)
if len(ret) == 0 {
panic("no return value specified for ListRepositories")
@ -1894,19 +1894,19 @@ func (_m *Store) ListRepositories(ctx context.Context) ([]params.Repository, err
var r0 []params.Repository
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]params.Repository, error)); ok {
return rf(ctx)
if rf, ok := ret.Get(0).(func(context.Context, params.RepositoryFilter) ([]params.Repository, error)); ok {
return rf(ctx, filter)
}
if rf, ok := ret.Get(0).(func(context.Context) []params.Repository); ok {
r0 = rf(ctx)
if rf, ok := ret.Get(0).(func(context.Context, params.RepositoryFilter) []params.Repository); ok {
r0 = rf(ctx, filter)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Repository)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
if rf, ok := ret.Get(1).(func(context.Context, params.RepositoryFilter) error); ok {
r1 = rf(ctx, filter)
} else {
r1 = ret.Error(1)
}

View file

@ -41,7 +41,7 @@ type RepoStore interface {
CreateRepository(ctx context.Context, owner, name string, credentials params.ForgeCredentials, webhookSecret string, poolBalancerType params.PoolBalancerType) (param params.Repository, err error)
GetRepository(ctx context.Context, owner, name, endpointName string) (params.Repository, error)
GetRepositoryByID(ctx context.Context, repoID string) (params.Repository, error)
ListRepositories(ctx context.Context) ([]params.Repository, error)
ListRepositories(ctx context.Context, filter params.RepositoryFilter) ([]params.Repository, error)
DeleteRepository(ctx context.Context, repoID string) error
UpdateRepository(ctx context.Context, repoID string, param params.UpdateEntityParams) (params.Repository, error)
}
@ -50,7 +50,7 @@ type OrgStore interface {
CreateOrganization(ctx context.Context, name string, credentials params.ForgeCredentials, webhookSecret string, poolBalancerType params.PoolBalancerType) (org params.Organization, err error)
GetOrganization(ctx context.Context, name, endpointName string) (params.Organization, error)
GetOrganizationByID(ctx context.Context, orgID string) (params.Organization, error)
ListOrganizations(ctx context.Context) ([]params.Organization, error)
ListOrganizations(ctx context.Context, filter params.OrganizationFilter) ([]params.Organization, error)
DeleteOrganization(ctx context.Context, orgID string) error
UpdateOrganization(ctx context.Context, orgID string, param params.UpdateEntityParams) (params.Organization, error)
}
@ -59,7 +59,7 @@ type EnterpriseStore interface {
CreateEnterprise(ctx context.Context, name string, credentialsName params.ForgeCredentials, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Enterprise, error)
GetEnterprise(ctx context.Context, name, endpointName string) (params.Enterprise, error)
GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error)
ListEnterprises(ctx context.Context) ([]params.Enterprise, error)
ListEnterprises(ctx context.Context, filter params.EnterpriseFilter) ([]params.Enterprise, error)
DeleteEnterprise(ctx context.Context, enterpriseID string) error
UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateEntityParams) (params.Enterprise, error)
}

View file

@ -111,13 +111,19 @@ func (s *sqlDatabase) GetEnterpriseByID(ctx context.Context, enterpriseID string
return param, nil
}
func (s *sqlDatabase) ListEnterprises(_ context.Context) ([]params.Enterprise, error) {
func (s *sqlDatabase) ListEnterprises(_ context.Context, filter params.EnterpriseFilter) ([]params.Enterprise, error) {
var enterprises []Enterprise
q := s.conn.
Preload("Credentials").
Preload("Credentials.Endpoint").
Preload("Endpoint").
Find(&enterprises)
Preload("Endpoint")
if filter.Name != "" {
q = q.Where("name = ?", filter.Name)
}
if filter.Endpoint != "" {
q = q.Where("endpoint_name = ?", filter.Endpoint)
}
q = q.Find(&enterprises)
if q.Error != nil {
return []params.Enterprise{}, errors.Wrap(q.Error, "fetching enterprises")
}

View file

@ -54,8 +54,10 @@ type EnterpriseTestSuite struct {
adminUserID string
testCreds params.ForgeCredentials
ghesCreds params.ForgeCredentials
secondaryTestCreds params.ForgeCredentials
githubEndpoint params.ForgeEndpoint
ghesEndpoint params.ForgeEndpoint
}
func (s *EnterpriseTestSuite) equalInstancesByName(expected, actual []params.Instance) {
@ -90,7 +92,9 @@ func (s *EnterpriseTestSuite) SetupTest() {
s.Require().NotEmpty(s.adminUserID)
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.ghesEndpoint = garmTesting.CreateGHESEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.ghesCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "ghes-creds", db, s.T(), s.ghesEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some enterprise objects in the database, for testing purposes
@ -272,18 +276,68 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseDBDecryptingErr() {
}
func (s *EnterpriseTestSuite) TestListEnterprises() {
enterprises, err := s.Store.ListEnterprises(s.adminCtx)
enterprises, err := s.Store.ListEnterprises(s.adminCtx, params.EnterpriseFilter{})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), s.Fixtures.Enterprises, enterprises)
}
func (s *EnterpriseTestSuite) TestListEnterprisesWithFilter() {
enterprise, err := s.Store.CreateEnterprise(
s.adminCtx,
"test-enterprise",
s.ghesCreds,
"test-secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
enterprise2, err := s.Store.CreateEnterprise(
s.adminCtx,
"test-enterprise",
s.testCreds,
"test-secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
enterprise3, err := s.Store.CreateEnterprise(
s.adminCtx,
"test-enterprise2",
s.testCreds,
"test-secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
enterprises, err := s.Store.ListEnterprises(s.adminCtx, params.EnterpriseFilter{
Name: "test-enterprise",
})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Enterprise{enterprise, enterprise2}, enterprises)
enterprises, err = s.Store.ListEnterprises(s.adminCtx, params.EnterpriseFilter{
Name: "test-enterprise",
Endpoint: s.ghesEndpoint.Name,
})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Enterprise{enterprise}, enterprises)
enterprises, err = s.Store.ListEnterprises(s.adminCtx, params.EnterpriseFilter{
Name: "test-enterprise2",
})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Enterprise{enterprise3}, enterprises)
}
func (s *EnterpriseTestSuite) TestListEnterprisesDBFetchErr() {
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `enterprises` WHERE `enterprises`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("fetching user from database mock error"))
_, err := s.StoreSQLMocked.ListEnterprises(s.adminCtx)
_, err := s.StoreSQLMocked.ListEnterprises(s.adminCtx, params.EnterpriseFilter{})
s.assertSQLMockExpectations()
s.Require().NotNil(err)

View file

@ -92,15 +92,23 @@ func (s *sqlDatabase) GetOrganization(ctx context.Context, name, endpointName st
return param, nil
}
func (s *sqlDatabase) ListOrganizations(_ context.Context) ([]params.Organization, error) {
func (s *sqlDatabase) ListOrganizations(_ context.Context, filter params.OrganizationFilter) ([]params.Organization, error) {
var orgs []Organization
q := s.conn.
Preload("Credentials").
Preload("GiteaCredentials").
Preload("Credentials.Endpoint").
Preload("GiteaCredentials.Endpoint").
Preload("Endpoint").
Find(&orgs)
Preload("Endpoint")
if filter.Name != "" {
q = q.Where("name = ?", filter.Name)
}
if filter.Endpoint != "" {
q = q.Where("endpoint_name = ?", filter.Endpoint)
}
q = q.Find(&orgs)
if q.Error != nil {
return []params.Organization{}, errors.Wrap(q.Error, "fetching org from database")
}

View file

@ -333,18 +333,74 @@ func (s *OrgTestSuite) TestGetOrganizationDBDecryptingErr() {
}
func (s *OrgTestSuite) TestListOrganizations() {
orgs, err := s.Store.ListOrganizations(s.adminCtx)
orgs, err := s.Store.ListOrganizations(s.adminCtx, params.OrganizationFilter{})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), s.Fixtures.Orgs, orgs)
}
func (s *OrgTestSuite) TestListOrganizationsWithFilters() {
org, err := s.Store.CreateOrganization(
s.adminCtx,
"test-org",
s.testCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
org2, err := s.Store.CreateOrganization(
s.adminCtx,
"test-org",
s.testCredsGitea,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
org3, err := s.Store.CreateOrganization(
s.adminCtx,
"test-org2",
s.testCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
orgs, err := s.Store.ListOrganizations(
s.adminCtx,
params.OrganizationFilter{
Name: "test-org",
})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Organization{org, org2}, orgs)
orgs, err = s.Store.ListOrganizations(
s.adminCtx,
params.OrganizationFilter{
Name: "test-org",
Endpoint: s.giteaEndpoint.Name,
})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Organization{org2}, orgs)
orgs, err = s.Store.ListOrganizations(
s.adminCtx,
params.OrganizationFilter{
Name: "test-org2",
})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Organization{org3}, orgs)
}
func (s *OrgTestSuite) TestListOrganizationsDBFetchErr() {
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `organizations` WHERE `organizations`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("fetching user from database mock error"))
_, err := s.StoreSQLMocked.ListOrganizations(s.adminCtx)
_, err := s.StoreSQLMocked.ListOrganizations(s.adminCtx, params.OrganizationFilter{})
s.assertSQLMockExpectations()
s.Require().NotNil(err)

View file

@ -93,15 +93,24 @@ func (s *sqlDatabase) GetRepository(ctx context.Context, owner, name, endpointNa
return param, nil
}
func (s *sqlDatabase) ListRepositories(_ context.Context) ([]params.Repository, error) {
func (s *sqlDatabase) ListRepositories(_ context.Context, filter params.RepositoryFilter) ([]params.Repository, error) {
var repos []Repository
q := s.conn.
Preload("Credentials").
Preload("GiteaCredentials").
Preload("Credentials.Endpoint").
Preload("GiteaCredentials.Endpoint").
Preload("Endpoint").
Find(&repos)
Preload("Endpoint")
if filter.Owner != "" {
q = q.Where("owner = ?", filter.Owner)
}
if filter.Name != "" {
q = q.Where("name = ?", filter.Name)
}
if filter.Endpoint != "" {
q = q.Where("endpoint_name = ?", filter.Endpoint)
}
q = q.Find(&repos)
if q.Error != nil {
return []params.Repository{}, errors.Wrap(q.Error, "fetching user from database")
}

View file

@ -376,18 +376,99 @@ func (s *RepoTestSuite) TestGetRepositoryDBDecryptingErr() {
}
func (s *RepoTestSuite) TestListRepositories() {
repos, err := s.Store.ListRepositories(s.adminCtx)
repos, err := s.Store.ListRepositories(s.adminCtx, params.RepositoryFilter{})
s.Require().Nil(err)
s.equalReposByName(s.Fixtures.Repos, repos)
}
func (s *RepoTestSuite) TestListRepositoriesWithFilters() {
repo, err := s.Store.CreateRepository(
s.adminCtx,
"test-owner",
"test-repo",
s.testCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
repo2, err := s.Store.CreateRepository(
s.adminCtx,
"test-owner",
"test-repo",
s.testCredsGitea,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
repo3, err := s.Store.CreateRepository(
s.adminCtx,
"test-owner",
"test-repo2",
s.testCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
repo4, err := s.Store.CreateRepository(
s.adminCtx,
"test-owner2",
"test-repo",
s.testCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
repos, err := s.Store.ListRepositories(
s.adminCtx,
params.RepositoryFilter{
Name: "test-repo",
})
s.Require().Nil(err)
s.equalReposByName([]params.Repository{repo, repo2, repo4}, repos)
repos, err = s.Store.ListRepositories(
s.adminCtx,
params.RepositoryFilter{
Name: "test-repo",
Owner: "test-owner",
})
s.Require().Nil(err)
s.equalReposByName([]params.Repository{repo, repo2}, repos)
repos, err = s.Store.ListRepositories(
s.adminCtx,
params.RepositoryFilter{
Name: "test-repo",
Owner: "test-owner",
Endpoint: s.giteaEndpoint.Name,
})
s.Require().Nil(err)
s.equalReposByName([]params.Repository{repo2}, repos)
repos, err = s.Store.ListRepositories(
s.adminCtx,
params.RepositoryFilter{
Name: "test-repo2",
})
s.Require().Nil(err)
s.equalReposByName([]params.Repository{repo3}, repos)
}
func (s *RepoTestSuite) TestListRepositoriesDBFetchErr() {
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE `repositories`.`deleted_at` IS NULL")).
WillReturnError(fmt.Errorf("fetching user from database mock error"))
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx)
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx, params.RepositoryFilter{})
s.Require().NotNil(err)
s.Require().Equal("fetching user from database: fetching user from database mock error", err.Error())
@ -401,7 +482,7 @@ func (s *RepoTestSuite) TestListRepositoriesDBDecryptingErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE `repositories`.`deleted_at` IS NULL")).
WillReturnRows(sqlmock.NewRows([]string{"id", "webhook_secret"}).AddRow(s.Fixtures.Repos[0].ID, s.Fixtures.Repos[0].WebhookSecret))
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx)
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx, params.RepositoryFilter{})
s.Require().NotNil(err)
s.Require().Equal("fetching repositories: decrypting secret: invalid passphrase length (expected length 32 characters)", err.Error())

View file

@ -85,6 +85,31 @@ func CreateGARMTestUser(ctx context.Context, username string, db common.Store, s
return user
}
func CreateGHESEndpoint(ctx context.Context, db common.Store, s *testing.T) params.ForgeEndpoint {
endpointParams := params.CreateGithubEndpointParams{
Name: "ghes.example.com",
Description: "GHES endpoint",
APIBaseURL: "https://ghes.example.com",
UploadBaseURL: "https://upload.ghes.example.com/",
BaseURL: "https://ghes.example.com",
}
ep, err := db.GetGithubEndpoint(ctx, endpointParams.Name)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
s.Fatalf("failed to get database object (%s): %v", endpointParams.Name, err)
}
ep, err = db.CreateGithubEndpoint(ctx, endpointParams)
if err != nil {
if !errors.Is(err, runnerErrors.ErrDuplicateEntity) {
s.Fatalf("failed to create database object (%s): %v", endpointParams.Name, err)
}
}
}
return ep
}
func CreateDefaultGithubEndpoint(ctx context.Context, db common.Store, s *testing.T) params.ForgeEndpoint {
endpointParams := params.CreateGithubEndpointParams{
Name: "github.com",

View file

@ -1192,3 +1192,19 @@ type ForgeEndpoint struct {
EndpointType EndpointType `json:"endpoint_type,omitempty"`
}
type RepositoryFilter struct {
Owner string
Name string
Endpoint string
}
type OrganizationFilter struct {
Name string
Endpoint string
}
type EnterpriseFilter struct {
Name string
Endpoint string
}

View file

@ -86,12 +86,12 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
return enterprise, nil
}
func (r *Runner) ListEnterprises(ctx context.Context) ([]params.Enterprise, error) {
func (r *Runner) ListEnterprises(ctx context.Context, filter params.EnterpriseFilter) ([]params.Enterprise, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
enterprises, err := r.store.ListEnterprises(ctx)
enterprises, err := r.store.ListEnterprises(ctx, filter)
if err != nil {
return nil, errors.Wrap(err, "listing enterprises")
}

View file

@ -59,6 +59,8 @@ type EnterpriseTestSuite struct {
testCreds params.ForgeCredentials
secondaryTestCreds params.ForgeCredentials
forgeEndpoint params.ForgeEndpoint
ghesEndpoint params.ForgeEndpoint
ghesCreds params.ForgeCredentials
}
func (s *EnterpriseTestSuite) SetupTest() {
@ -71,8 +73,10 @@ func (s *EnterpriseTestSuite) SetupTest() {
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.forgeEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.ghesEndpoint = garmTesting.CreateGHESEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.forgeEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.forgeEndpoint)
s.ghesCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "ghes-creds", db, s.T(), s.ghesEndpoint)
// create some organization objects in the database, for testing purposes
enterprises := map[string]params.Enterprise{}
@ -224,14 +228,74 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseStartPoolMgrFailed() {
func (s *EnterpriseTestSuite) TestListEnterprises() {
s.Fixtures.PoolMgrCtrlMock.On("GetEnterprisePoolManager", mock.AnythingOfType("params.Enterprise")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
orgs, err := s.Runner.ListEnterprises(s.Fixtures.AdminContext)
orgs, err := s.Runner.ListEnterprises(s.Fixtures.AdminContext, params.EnterpriseFilter{})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), garmTesting.DBEntityMapToSlice(s.Fixtures.StoreEnterprises), orgs)
}
func (s *EnterpriseTestSuite) TestListEnterprisesWithFilters() {
s.Fixtures.PoolMgrCtrlMock.On("GetEnterprisePoolManager", mock.AnythingOfType("params.Enterprise")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
enterprise, err := s.Fixtures.Store.CreateEnterprise(
s.Fixtures.AdminContext,
"test-enterprise",
s.testCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
enterprise2, err := s.Fixtures.Store.CreateEnterprise(
s.Fixtures.AdminContext,
"test-enterprise2",
s.testCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
enterprise3, err := s.Fixtures.Store.CreateEnterprise(
s.Fixtures.AdminContext,
"test-enterprise",
s.ghesCreds,
"super secret",
params.PoolBalancerTypeRoundRobin,
)
s.Require().NoError(err)
orgs, err := s.Runner.ListEnterprises(
s.Fixtures.AdminContext,
params.EnterpriseFilter{
Name: "test-enterprise",
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Enterprise{enterprise, enterprise3}, orgs)
orgs, err = s.Runner.ListEnterprises(
s.Fixtures.AdminContext,
params.EnterpriseFilter{
Name: "test-enterprise",
Endpoint: s.ghesEndpoint.Name,
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Enterprise{enterprise3}, orgs)
orgs, err = s.Runner.ListEnterprises(
s.Fixtures.AdminContext,
params.EnterpriseFilter{
Name: "test-enterprise2",
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Enterprise{enterprise2}, orgs)
}
func (s *EnterpriseTestSuite) TestListEnterprisesErrUnauthorized() {
_, err := s.Runner.ListEnterprises(context.Background())
_, err := s.Runner.ListEnterprises(context.Background(), params.EnterpriseFilter{})
s.Require().Equal(runnerErrors.ErrUnauthorized, err)
}

View file

@ -19,6 +19,7 @@ import (
"strconv"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner" //nolint:typecheck
)
@ -28,7 +29,7 @@ func CollectEnterpriseMetric(ctx context.Context, r *runner.Runner) error {
metrics.EnterpriseInfo.Reset()
metrics.EnterprisePoolManagerStatus.Reset()
enterprises, err := r.ListEnterprises(ctx)
enterprises, err := r.ListEnterprises(ctx, params.EnterpriseFilter{})
if err != nil {
return err
}

View file

@ -19,6 +19,7 @@ import (
"strconv"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner"
)
@ -28,7 +29,7 @@ func CollectOrganizationMetric(ctx context.Context, r *runner.Runner) error {
metrics.OrganizationInfo.Reset()
metrics.OrganizationPoolManagerStatus.Reset()
organizations, err := r.ListOrganizations(ctx)
organizations, err := r.ListOrganizations(ctx, params.OrganizationFilter{})
if err != nil {
return err
}

View file

@ -19,6 +19,7 @@ import (
"strconv"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner"
)
@ -27,7 +28,7 @@ func CollectRepositoryMetric(ctx context.Context, r *runner.Runner) error {
metrics.EnterpriseInfo.Reset()
metrics.EnterprisePoolManagerStatus.Reset()
repositories, err := r.ListRepositories(ctx)
repositories, err := r.ListRepositories(ctx, params.RepositoryFilter{})
if err != nil {
return err
}

View file

@ -95,12 +95,12 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
return org, nil
}
func (r *Runner) ListOrganizations(ctx context.Context) ([]params.Organization, error) {
func (r *Runner) ListOrganizations(ctx context.Context, filter params.OrganizationFilter) ([]params.Organization, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
orgs, err := r.store.ListOrganizations(ctx)
orgs, err := r.store.ListOrganizations(ctx, filter)
if err != nil {
return nil, errors.Wrap(err, "listing organizations")
}

View file

@ -58,7 +58,9 @@ type OrgTestSuite struct {
testCreds params.ForgeCredentials
secondaryTestCreds params.ForgeCredentials
giteaTestCreds params.ForgeCredentials
githubEndpoint params.ForgeEndpoint
giteaEndpoint params.ForgeEndpoint
}
func (s *OrgTestSuite) SetupTest() {
@ -72,7 +74,9 @@ func (s *OrgTestSuite) SetupTest() {
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.giteaEndpoint = garmTesting.CreateDefaultGiteaEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.giteaTestCreds = garmTesting.CreateTestGiteaCredentials(adminCtx, "gitea-creds", db, s.T(), s.giteaEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
// create some organization objects in the database, for testing purposes
@ -238,14 +242,74 @@ func (s *OrgTestSuite) TestCreateOrganizationStartPoolMgrFailed() {
func (s *OrgTestSuite) TestListOrganizations() {
s.Fixtures.PoolMgrCtrlMock.On("GetOrgPoolManager", mock.AnythingOfType("params.Organization")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
orgs, err := s.Runner.ListOrganizations(s.Fixtures.AdminContext)
orgs, err := s.Runner.ListOrganizations(s.Fixtures.AdminContext, params.OrganizationFilter{})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), garmTesting.DBEntityMapToSlice(s.Fixtures.StoreOrgs), orgs)
}
func (s *OrgTestSuite) TestListOrganizationsWithFilter() {
s.Fixtures.PoolMgrCtrlMock.On("GetOrgPoolManager", mock.AnythingOfType("params.Organization")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
org, err := s.Fixtures.Store.CreateOrganization(
s.Fixtures.AdminContext,
"test-org",
s.testCreds,
"super-secret",
params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
org2, err := s.Fixtures.Store.CreateOrganization(
s.Fixtures.AdminContext,
"test-org",
s.giteaTestCreds,
"super-secret",
params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
org3, err := s.Fixtures.Store.CreateOrganization(
s.Fixtures.AdminContext,
"test-org2",
s.giteaTestCreds,
"super-secret",
params.PoolBalancerTypeRoundRobin)
s.Require().NoError(err)
orgs, err := s.Runner.ListOrganizations(
s.Fixtures.AdminContext,
params.OrganizationFilter{
Name: "test-org",
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Organization{org, org2}, orgs)
orgs, err = s.Runner.ListOrganizations(
s.Fixtures.AdminContext,
params.OrganizationFilter{
Name: "test-org",
Endpoint: s.giteaEndpoint.Name,
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Organization{org2}, orgs)
orgs, err = s.Runner.ListOrganizations(
s.Fixtures.AdminContext,
params.OrganizationFilter{
Name: "test-org2",
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Organization{org3}, orgs)
}
func (s *OrgTestSuite) TestListOrganizationsErrUnauthorized() {
_, err := s.Runner.ListOrganizations(context.Background())
_, err := s.Runner.ListOrganizations(context.Background(), params.OrganizationFilter{})
s.Require().Equal(runnerErrors.ErrUnauthorized, err)
}

View file

@ -93,12 +93,12 @@ func (r *Runner) CreateRepository(ctx context.Context, param params.CreateRepoPa
return repo, nil
}
func (r *Runner) ListRepositories(ctx context.Context) ([]params.Repository, error) {
func (r *Runner) ListRepositories(ctx context.Context, filter params.RepositoryFilter) ([]params.Repository, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
repos, err := r.store.ListRepositories(ctx)
repos, err := r.store.ListRepositories(ctx, filter)
if err != nil {
return nil, errors.Wrap(err, "listing repositories")
}

View file

@ -62,7 +62,9 @@ type RepoTestSuite struct {
testCreds params.ForgeCredentials
secondaryTestCreds params.ForgeCredentials
giteaTestCreds params.ForgeCredentials
githubEndpoint params.ForgeEndpoint
giteaEndpoint params.ForgeEndpoint
}
func (s *RepoTestSuite) SetupTest() {
@ -75,8 +77,10 @@ func (s *RepoTestSuite) SetupTest() {
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.githubEndpoint = garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())
s.giteaEndpoint = garmTesting.CreateDefaultGiteaEndpoint(adminCtx, db, s.T())
s.testCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "new-creds", db, s.T(), s.githubEndpoint)
s.secondaryTestCreds = garmTesting.CreateTestGithubCredentials(adminCtx, "secondary-creds", db, s.T(), s.githubEndpoint)
s.giteaTestCreds = garmTesting.CreateTestGiteaCredentials(adminCtx, "gitea-creds", db, s.T(), s.giteaEndpoint)
// create some repository objects in the database, for testing purposes
repos := map[string]params.Repository{}
@ -254,14 +258,81 @@ func (s *RepoTestSuite) TestCreateRepositoryStartPoolMgrFailed() {
func (s *RepoTestSuite) TestListRepositories() {
s.Fixtures.PoolMgrCtrlMock.On("GetRepoPoolManager", mock.AnythingOfType("params.Repository")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
repos, err := s.Runner.ListRepositories(s.Fixtures.AdminContext)
repos, err := s.Runner.ListRepositories(s.Fixtures.AdminContext, params.RepositoryFilter{})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), garmTesting.DBEntityMapToSlice(s.Fixtures.StoreRepos), repos)
}
func (s *RepoTestSuite) TestListRepositoriesWithFilters() {
s.Fixtures.PoolMgrCtrlMock.On("GetRepoPoolManager", mock.AnythingOfType("params.Repository")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
repo, err := s.Fixtures.Store.CreateRepository(
s.Fixtures.AdminContext,
"example-owner",
"example-repo",
s.testCreds,
"test-webhook-secret",
params.PoolBalancerTypeRoundRobin,
)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create database object (example-repo): %q", err))
}
repo2, err := s.Fixtures.Store.CreateRepository(
s.Fixtures.AdminContext,
"another-example-owner",
"example-repo",
s.testCreds,
"test-webhook-secret",
params.PoolBalancerTypeRoundRobin,
)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create database object (example-repo): %q", err))
}
repo3, err := s.Fixtures.Store.CreateRepository(
s.Fixtures.AdminContext,
"example-owner",
"example-repo",
s.giteaTestCreds,
"test-webhook-secret",
params.PoolBalancerTypeRoundRobin,
)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create database object (example-repo): %q", err))
}
repos, err := s.Runner.ListRepositories(s.Fixtures.AdminContext, params.RepositoryFilter{Name: "example-repo"})
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Repository{repo, repo2, repo3}, repos)
repos, err = s.Runner.ListRepositories(
s.Fixtures.AdminContext,
params.RepositoryFilter{
Name: "example-repo",
Owner: "example-owner",
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Repository{repo, repo3}, repos)
repos, err = s.Runner.ListRepositories(
s.Fixtures.AdminContext,
params.RepositoryFilter{
Name: "example-repo",
Owner: "example-owner",
Endpoint: s.giteaEndpoint.Name,
},
)
s.Require().Nil(err)
garmTesting.EqualDBEntityByName(s.T(), []params.Repository{repo3}, repos)
}
func (s *RepoTestSuite) TestListRepositoriesErrUnauthorized() {
_, err := s.Runner.ListRepositories(context.Background())
_, err := s.Runner.ListRepositories(context.Background(), params.RepositoryFilter{})
s.Require().Equal(runnerErrors.ErrUnauthorized, err)
}

View file

@ -327,17 +327,17 @@ func (r *Runner) loadReposOrgsAndEnterprises() error {
r.mux.Lock()
defer r.mux.Unlock()
repos, err := r.store.ListRepositories(r.ctx)
repos, err := r.store.ListRepositories(r.ctx, params.RepositoryFilter{})
if err != nil {
return errors.Wrap(err, "fetching repositories")
}
orgs, err := r.store.ListOrganizations(r.ctx)
orgs, err := r.store.ListOrganizations(r.ctx, params.OrganizationFilter{})
if err != nil {
return errors.Wrap(err, "fetching organizations")
}
enterprises, err := r.store.ListEnterprises(r.ctx)
enterprises, err := r.store.ListEnterprises(r.ctx, params.EnterpriseFilter{})
if err != nil {
return errors.Wrap(err, "fetching enterprises")
}

View file

@ -96,17 +96,17 @@ func (w *Worker) loadAllEntities() error {
return fmt.Errorf("listing scale sets: %w", err)
}
repos, err := w.store.ListRepositories(w.ctx)
repos, err := w.store.ListRepositories(w.ctx, params.RepositoryFilter{})
if err != nil {
return fmt.Errorf("listing repositories: %w", err)
}
orgs, err := w.store.ListOrganizations(w.ctx)
orgs, err := w.store.ListOrganizations(w.ctx, params.OrganizationFilter{})
if err != nil {
return fmt.Errorf("listing organizations: %w", err)
}
enterprises, err := w.store.ListEnterprises(w.ctx)
enterprises, err := w.store.ListEnterprises(w.ctx, params.EnterpriseFilter{})
if err != nil {
return fmt.Errorf("listing enterprises: %w", err)
}

View file

@ -24,6 +24,7 @@ import (
"github.com/cloudbase/garm/auth"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/database/watcher"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
garmUtil "github.com/cloudbase/garm/util"
)
@ -63,7 +64,7 @@ type Controller struct {
func (c *Controller) loadAllRepositories() error {
c.mux.Lock()
defer c.mux.Unlock()
repos, err := c.store.ListRepositories(c.ctx)
repos, err := c.store.ListRepositories(c.ctx, params.RepositoryFilter{})
if err != nil {
return fmt.Errorf("fetching repositories: %w", err)
}
@ -95,7 +96,7 @@ func (c *Controller) loadAllRepositories() error {
func (c *Controller) loadAllOrganizations() error {
c.mux.Lock()
defer c.mux.Unlock()
orgs, err := c.store.ListOrganizations(c.ctx)
orgs, err := c.store.ListOrganizations(c.ctx, params.OrganizationFilter{})
if err != nil {
return fmt.Errorf("fetching organizations: %w", err)
}
@ -127,7 +128,7 @@ func (c *Controller) loadAllOrganizations() error {
func (c *Controller) loadAllEnterprises() error {
c.mux.Lock()
defer c.mux.Unlock()
enterprises, err := c.store.ListEnterprises(c.ctx)
enterprises, err := c.store.ListEnterprises(c.ctx, params.EnterpriseFilter{})
if err != nil {
return fmt.Errorf("fetching enterprises: %w", err)
}