Deduplicate code

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2024-03-17 10:21:41 +00:00
parent 234f71d9d1
commit 1734e6f87c
11 changed files with 1260 additions and 1863 deletions

View file

@ -647,9 +647,24 @@ type UpdateSystemInfoParams struct {
type GithubEntity struct {
Owner string `json:"owner"`
Name string `json:"name"`
ID string `json:"id"`
EntityType GithubEntityType `json:"entity_type"`
WebhookSecret string `json:"-"`
}
func (g GithubEntity) LabelScope() string {
return cases.Title(language.English, cases.NoLower).String(g.EntityType.String())
}
func (g GithubEntity) String() string {
switch g.EntityType {
case GithubEntityTypeRepository:
return fmt.Sprintf("%s/%s", g.Owner, g.Name)
case GithubEntityTypeOrganization:
return g.Owner
case GithubEntityTypeEnterprise:
return g.Owner
}
return ""
}

View file

@ -7,6 +7,8 @@ import (
github "github.com/google/go-github/v57/github"
mock "github.com/stretchr/testify/mock"
params "github.com/cloudbase/garm/params"
)
// GithubClient is an autogenerated mock type for the GithubClient type
@ -14,185 +16,29 @@ type GithubClient struct {
mock.Mock
}
// CreateOrgHook provides a mock function with given fields: ctx, org, hook
func (_m *GithubClient) CreateOrgHook(ctx context.Context, org string, hook *github.Hook) (*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, org, hook)
// CreateEntityHook provides a mock function with given fields: ctx, hook
func (_m *GithubClient) CreateEntityHook(ctx context.Context, hook *github.Hook) (*github.Hook, error) {
ret := _m.Called(ctx, hook)
if len(ret) == 0 {
panic("no return value specified for CreateOrgHook")
panic("no return value specified for CreateEntityHook")
}
var r0 *github.Hook
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, *github.Hook) (*github.Hook, *github.Response, error)); ok {
return rf(ctx, org, hook)
}
if rf, ok := ret.Get(0).(func(context.Context, string, *github.Hook) *github.Hook); ok {
r0 = rf(ctx, org, hook)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, *github.Hook) *github.Response); ok {
r1 = rf(ctx, org, hook)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, *github.Hook) error); ok {
r2 = rf(ctx, org, hook)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// CreateOrganizationRegistrationToken provides a mock function with given fields: ctx, owner
func (_m *GithubClient) CreateOrganizationRegistrationToken(ctx context.Context, owner string) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx, owner)
if len(ret) == 0 {
panic("no return value specified for CreateOrganizationRegistrationToken")
}
var r0 *github.RegistrationToken
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string) (*github.RegistrationToken, *github.Response, error)); ok {
return rf(ctx, owner)
}
if rf, ok := ret.Get(0).(func(context.Context, string) *github.RegistrationToken); ok {
r0 = rf(ctx, owner)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RegistrationToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) *github.Response); ok {
r1 = rf(ctx, owner)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
r2 = rf(ctx, owner)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// CreateRegistrationToken provides a mock function with given fields: ctx, owner, repo
func (_m *GithubClient) CreateRegistrationToken(ctx context.Context, owner string, repo string) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx, owner, repo)
if len(ret) == 0 {
panic("no return value specified for CreateRegistrationToken")
}
var r0 *github.RegistrationToken
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*github.RegistrationToken, *github.Response, error)); ok {
return rf(ctx, owner, repo)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) *github.RegistrationToken); ok {
r0 = rf(ctx, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RegistrationToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) *github.Response); ok {
r1 = rf(ctx, owner, repo)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string) error); ok {
r2 = rf(ctx, owner, repo)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// CreateRepoHook provides a mock function with given fields: ctx, owner, repo, hook
func (_m *GithubClient) CreateRepoHook(ctx context.Context, owner string, repo string, hook *github.Hook) (*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, hook)
if len(ret) == 0 {
panic("no return value specified for CreateRepoHook")
}
var r0 *github.Hook
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.Hook) (*github.Hook, *github.Response, error)); ok {
return rf(ctx, owner, repo, hook)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.Hook) *github.Hook); ok {
r0 = rf(ctx, owner, repo, hook)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.Hook) *github.Response); ok {
r1 = rf(ctx, owner, repo, hook)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.Hook) error); ok {
r2 = rf(ctx, owner, repo, hook)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// DeleteOrgHook provides a mock function with given fields: ctx, org, id
func (_m *GithubClient) DeleteOrgHook(ctx context.Context, org string, id int64) (*github.Response, error) {
ret := _m.Called(ctx, org, id)
if len(ret) == 0 {
panic("no return value specified for DeleteOrgHook")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, int64) (*github.Response, error)); ok {
return rf(ctx, org, id)
if rf, ok := ret.Get(0).(func(context.Context, *github.Hook) (*github.Hook, error)); ok {
return rf(ctx, hook)
}
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *github.Response); ok {
r0 = rf(ctx, org, id)
if rf, ok := ret.Get(0).(func(context.Context, *github.Hook) *github.Hook); ok {
r0 = rf(ctx, hook)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
r0 = ret.Get(0).(*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
r1 = rf(ctx, org, id)
if rf, ok := ret.Get(1).(func(context.Context, *github.Hook) error); ok {
r1 = rf(ctx, hook)
} else {
r1 = ret.Error(1)
}
@ -200,29 +46,68 @@ func (_m *GithubClient) DeleteOrgHook(ctx context.Context, org string, id int64)
return r0, r1
}
// DeleteRepoHook provides a mock function with given fields: ctx, owner, repo, id
func (_m *GithubClient) DeleteRepoHook(ctx context.Context, owner string, repo string, id int64) (*github.Response, error) {
ret := _m.Called(ctx, owner, repo, id)
// CreateEntityRegistrationToken provides a mock function with given fields: ctx
func (_m *GithubClient) CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for DeleteRepoHook")
panic("no return value specified for CreateEntityRegistrationToken")
}
var r0 *github.RegistrationToken
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context) (*github.RegistrationToken, *github.Response, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) *github.RegistrationToken); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RegistrationToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context) *github.Response); ok {
r1 = rf(ctx)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context) error); ok {
r2 = rf(ctx)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// DeleteEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubClient) DeleteEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for DeleteEntityHook")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*github.Response, error)); ok {
return rf(ctx, owner, repo, id)
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *github.Response); ok {
r0 = rf(ctx, owner, repo, id)
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
r1 = rf(ctx, owner, repo, id)
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
@ -230,155 +115,68 @@ func (_m *GithubClient) DeleteRepoHook(ctx context.Context, owner string, repo s
return r0, r1
}
// GenerateOrgJITConfig provides a mock function with given fields: ctx, owner, request
func (_m *GithubClient) GenerateOrgJITConfig(ctx context.Context, owner string, request *github.GenerateJITConfigRequest) (*github.JITRunnerConfig, *github.Response, error) {
ret := _m.Called(ctx, owner, request)
// GetEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubClient) GetEntityHook(ctx context.Context, id int64) (*github.Hook, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for GenerateOrgJITConfig")
}
var r0 *github.JITRunnerConfig
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, *github.GenerateJITConfigRequest) (*github.JITRunnerConfig, *github.Response, error)); ok {
return rf(ctx, owner, request)
}
if rf, ok := ret.Get(0).(func(context.Context, string, *github.GenerateJITConfigRequest) *github.JITRunnerConfig); ok {
r0 = rf(ctx, owner, request)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.JITRunnerConfig)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, *github.GenerateJITConfigRequest) *github.Response); ok {
r1 = rf(ctx, owner, request)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, *github.GenerateJITConfigRequest) error); ok {
r2 = rf(ctx, owner, request)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// GenerateRepoJITConfig provides a mock function with given fields: ctx, owner, repo, request
func (_m *GithubClient) GenerateRepoJITConfig(ctx context.Context, owner string, repo string, request *github.GenerateJITConfigRequest) (*github.JITRunnerConfig, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, request)
if len(ret) == 0 {
panic("no return value specified for GenerateRepoJITConfig")
}
var r0 *github.JITRunnerConfig
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.GenerateJITConfigRequest) (*github.JITRunnerConfig, *github.Response, error)); ok {
return rf(ctx, owner, repo, request)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.GenerateJITConfigRequest) *github.JITRunnerConfig); ok {
r0 = rf(ctx, owner, repo, request)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.JITRunnerConfig)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.GenerateJITConfigRequest) *github.Response); ok {
r1 = rf(ctx, owner, repo, request)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.GenerateJITConfigRequest) error); ok {
r2 = rf(ctx, owner, repo, request)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// GetOrgHook provides a mock function with given fields: ctx, org, id
func (_m *GithubClient) GetOrgHook(ctx context.Context, org string, id int64) (*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, org, id)
if len(ret) == 0 {
panic("no return value specified for GetOrgHook")
panic("no return value specified for GetEntityHook")
}
var r0 *github.Hook
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, int64) (*github.Hook, *github.Response, error)); ok {
return rf(ctx, org, id)
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Hook, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *github.Hook); ok {
r0 = rf(ctx, org, id)
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Hook); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, int64) *github.Response); ok {
r1 = rf(ctx, org, id)
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
r1 = ret.Error(1)
}
if rf, ok := ret.Get(2).(func(context.Context, string, int64) error); ok {
r2 = rf(ctx, org, id)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
return r0, r1
}
// GetRepoHook provides a mock function with given fields: ctx, owner, repo, id
func (_m *GithubClient) GetRepoHook(ctx context.Context, owner string, repo string, id int64) (*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, id)
// GetEntityJITConfig provides a mock function with given fields: ctx, instance, pool, labels
func (_m *GithubClient) GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (map[string]string, *github.Runner, error) {
ret := _m.Called(ctx, instance, pool, labels)
if len(ret) == 0 {
panic("no return value specified for GetRepoHook")
panic("no return value specified for GetEntityJITConfig")
}
var r0 *github.Hook
var r1 *github.Response
var r0 map[string]string
var r1 *github.Runner
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*github.Hook, *github.Response, error)); ok {
return rf(ctx, owner, repo, id)
if rf, ok := ret.Get(0).(func(context.Context, string, params.Pool, []string) (map[string]string, *github.Runner, error)); ok {
return rf(ctx, instance, pool, labels)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *github.Hook); ok {
r0 = rf(ctx, owner, repo, id)
if rf, ok := ret.Get(0).(func(context.Context, string, params.Pool, []string) map[string]string); ok {
r0 = rf(ctx, instance, pool, labels)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Hook)
r0 = ret.Get(0).(map[string]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) *github.Response); ok {
r1 = rf(ctx, owner, repo, id)
if rf, ok := ret.Get(1).(func(context.Context, string, params.Pool, []string) *github.Runner); ok {
r1 = rf(ctx, instance, pool, labels)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
r1 = ret.Get(1).(*github.Runner)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string, int64) error); ok {
r2 = rf(ctx, owner, repo, id)
if rf, ok := ret.Get(2).(func(context.Context, string, params.Pool, []string) error); ok {
r2 = rf(ctx, instance, pool, labels)
} else {
r2 = ret.Error(2)
}
@ -425,38 +223,38 @@ func (_m *GithubClient) GetWorkflowJobByID(ctx context.Context, owner string, re
return r0, r1, r2
}
// ListOrgHooks provides a mock function with given fields: ctx, org, opts
func (_m *GithubClient) ListOrgHooks(ctx context.Context, org string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, org, opts)
// ListEntityHooks provides a mock function with given fields: ctx, opts
func (_m *GithubClient) ListEntityHooks(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, opts)
if len(ret) == 0 {
panic("no return value specified for ListOrgHooks")
panic("no return value specified for ListEntityHooks")
}
var r0 []*github.Hook
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOptions) ([]*github.Hook, *github.Response, error)); ok {
return rf(ctx, org, opts)
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) ([]*github.Hook, *github.Response, error)); ok {
return rf(ctx, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOptions) []*github.Hook); ok {
r0 = rf(ctx, org, opts)
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) []*github.Hook); ok {
r0 = rf(ctx, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, org, opts)
if rf, ok := ret.Get(1).(func(context.Context, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, *github.ListOptions) error); ok {
r2 = rf(ctx, org, opts)
if rf, ok := ret.Get(2).(func(context.Context, *github.ListOptions) error); ok {
r2 = rf(ctx, opts)
} else {
r2 = ret.Error(2)
}
@ -464,38 +262,38 @@ func (_m *GithubClient) ListOrgHooks(ctx context.Context, org string, opts *gith
return r0, r1, r2
}
// ListOrganizationRunnerApplicationDownloads provides a mock function with given fields: ctx, owner
func (_m *GithubClient) ListOrganizationRunnerApplicationDownloads(ctx context.Context, owner string) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx, owner)
// ListEntityRunnerApplicationDownloads provides a mock function with given fields: ctx
func (_m *GithubClient) ListEntityRunnerApplicationDownloads(ctx context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ListOrganizationRunnerApplicationDownloads")
panic("no return value specified for ListEntityRunnerApplicationDownloads")
}
var r0 []*github.RunnerApplicationDownload
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string) ([]*github.RunnerApplicationDownload, *github.Response, error)); ok {
return rf(ctx, owner)
if rf, ok := ret.Get(0).(func(context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context, string) []*github.RunnerApplicationDownload); ok {
r0 = rf(ctx, owner)
if rf, ok := ret.Get(0).(func(context.Context) []*github.RunnerApplicationDownload); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.RunnerApplicationDownload)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) *github.Response); ok {
r1 = rf(ctx, owner)
if rf, ok := ret.Get(1).(func(context.Context) *github.Response); ok {
r1 = rf(ctx)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
r2 = rf(ctx, owner)
if rf, ok := ret.Get(2).(func(context.Context) error); ok {
r2 = rf(ctx)
} else {
r2 = ret.Error(2)
}
@ -503,77 +301,38 @@ func (_m *GithubClient) ListOrganizationRunnerApplicationDownloads(ctx context.C
return r0, r1, r2
}
// ListOrganizationRunnerGroups provides a mock function with given fields: ctx, org, opts
func (_m *GithubClient) ListOrganizationRunnerGroups(ctx context.Context, org string, opts *github.ListOrgRunnerGroupOptions) (*github.RunnerGroups, *github.Response, error) {
ret := _m.Called(ctx, org, opts)
// ListEntityRunners provides a mock function with given fields: ctx, opts
func (_m *GithubClient) ListEntityRunners(ctx context.Context, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, opts)
if len(ret) == 0 {
panic("no return value specified for ListOrganizationRunnerGroups")
}
var r0 *github.RunnerGroups
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOrgRunnerGroupOptions) (*github.RunnerGroups, *github.Response, error)); ok {
return rf(ctx, org, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOrgRunnerGroupOptions) *github.RunnerGroups); ok {
r0 = rf(ctx, org, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RunnerGroups)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, *github.ListOrgRunnerGroupOptions) *github.Response); ok {
r1 = rf(ctx, org, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, *github.ListOrgRunnerGroupOptions) error); ok {
r2 = rf(ctx, org, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListOrganizationRunners provides a mock function with given fields: ctx, owner, opts
func (_m *GithubClient) ListOrganizationRunners(ctx context.Context, owner string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, owner, opts)
if len(ret) == 0 {
panic("no return value specified for ListOrganizationRunners")
panic("no return value specified for ListEntityRunners")
}
var r0 *github.Runners
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOptions) (*github.Runners, *github.Response, error)); ok {
return rf(ctx, owner, opts)
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) (*github.Runners, *github.Response, error)); ok {
return rf(ctx, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOptions) *github.Runners); ok {
r0 = rf(ctx, owner, opts)
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) *github.Runners); ok {
r0 = rf(ctx, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Runners)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, owner, opts)
if rf, ok := ret.Get(1).(func(context.Context, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, *github.ListOptions) error); ok {
r2 = rf(ctx, owner, opts)
if rf, ok := ret.Get(2).(func(context.Context, *github.ListOptions) error); ok {
r2 = rf(ctx, opts)
} else {
r2 = ret.Error(2)
}
@ -581,146 +340,29 @@ func (_m *GithubClient) ListOrganizationRunners(ctx context.Context, owner strin
return r0, r1, r2
}
// ListRepoHooks provides a mock function with given fields: ctx, owner, repo, opts
func (_m *GithubClient) ListRepoHooks(ctx context.Context, owner string, repo string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, opts)
// PingEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubClient) PingEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for ListRepoHooks")
}
var r0 []*github.Hook
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.ListOptions) ([]*github.Hook, *github.Response, error)); ok {
return rf(ctx, owner, repo, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.ListOptions) []*github.Hook); ok {
r0 = rf(ctx, owner, repo, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, owner, repo, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.ListOptions) error); ok {
r2 = rf(ctx, owner, repo, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListRunnerApplicationDownloads provides a mock function with given fields: ctx, owner, repo
func (_m *GithubClient) ListRunnerApplicationDownloads(ctx context.Context, owner string, repo string) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx, owner, repo)
if len(ret) == 0 {
panic("no return value specified for ListRunnerApplicationDownloads")
}
var r0 []*github.RunnerApplicationDownload
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]*github.RunnerApplicationDownload, *github.Response, error)); ok {
return rf(ctx, owner, repo)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) []*github.RunnerApplicationDownload); ok {
r0 = rf(ctx, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.RunnerApplicationDownload)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) *github.Response); ok {
r1 = rf(ctx, owner, repo)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string) error); ok {
r2 = rf(ctx, owner, repo)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListRunners provides a mock function with given fields: ctx, owner, repo, opts
func (_m *GithubClient) ListRunners(ctx context.Context, owner string, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, opts)
if len(ret) == 0 {
panic("no return value specified for ListRunners")
}
var r0 *github.Runners
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.ListOptions) (*github.Runners, *github.Response, error)); ok {
return rf(ctx, owner, repo, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.ListOptions) *github.Runners); ok {
r0 = rf(ctx, owner, repo, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Runners)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, owner, repo, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.ListOptions) error); ok {
r2 = rf(ctx, owner, repo, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// PingOrgHook provides a mock function with given fields: ctx, org, id
func (_m *GithubClient) PingOrgHook(ctx context.Context, org string, id int64) (*github.Response, error) {
ret := _m.Called(ctx, org, id)
if len(ret) == 0 {
panic("no return value specified for PingOrgHook")
panic("no return value specified for PingEntityHook")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, int64) (*github.Response, error)); ok {
return rf(ctx, org, id)
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *github.Response); ok {
r0 = rf(ctx, org, id)
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
r1 = rf(ctx, org, id)
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
@ -728,89 +370,29 @@ func (_m *GithubClient) PingOrgHook(ctx context.Context, org string, id int64) (
return r0, r1
}
// PingRepoHook provides a mock function with given fields: ctx, owner, repo, id
func (_m *GithubClient) PingRepoHook(ctx context.Context, owner string, repo string, id int64) (*github.Response, error) {
ret := _m.Called(ctx, owner, repo, id)
// RemoveEntityRunner provides a mock function with given fields: ctx, runnerID
func (_m *GithubClient) RemoveEntityRunner(ctx context.Context, runnerID int64) (*github.Response, error) {
ret := _m.Called(ctx, runnerID)
if len(ret) == 0 {
panic("no return value specified for PingRepoHook")
panic("no return value specified for RemoveEntityRunner")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*github.Response, error)); ok {
return rf(ctx, owner, repo, id)
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, runnerID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *github.Response); ok {
r0 = rf(ctx, owner, repo, id)
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
r0 = rf(ctx, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
r1 = rf(ctx, owner, repo, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveOrganizationRunner provides a mock function with given fields: ctx, owner, runnerID
func (_m *GithubClient) RemoveOrganizationRunner(ctx context.Context, owner string, runnerID int64) (*github.Response, error) {
ret := _m.Called(ctx, owner, runnerID)
if len(ret) == 0 {
panic("no return value specified for RemoveOrganizationRunner")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, int64) (*github.Response, error)); ok {
return rf(ctx, owner, runnerID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *github.Response); ok {
r0 = rf(ctx, owner, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
r1 = rf(ctx, owner, runnerID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveRunner provides a mock function with given fields: ctx, owner, repo, runnerID
func (_m *GithubClient) RemoveRunner(ctx context.Context, owner string, repo string, runnerID int64) (*github.Response, error) {
ret := _m.Called(ctx, owner, repo, runnerID)
if len(ret) == 0 {
panic("no return value specified for RemoveRunner")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*github.Response, error)); ok {
return rf(ctx, owner, repo, runnerID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *github.Response); ok {
r0 = rf(ctx, owner, repo, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
r1 = rf(ctx, owner, repo, runnerID)
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, runnerID)
} else {
r1 = ret.Error(1)
}

View file

@ -0,0 +1,376 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
package mocks
import (
context "context"
github "github.com/google/go-github/v57/github"
mock "github.com/stretchr/testify/mock"
params "github.com/cloudbase/garm/params"
)
// GithubEntityOperations is an autogenerated mock type for the GithubEntityOperations type
type GithubEntityOperations struct {
mock.Mock
}
// CreateEntityHook provides a mock function with given fields: ctx, hook
func (_m *GithubEntityOperations) CreateEntityHook(ctx context.Context, hook *github.Hook) (*github.Hook, error) {
ret := _m.Called(ctx, hook)
if len(ret) == 0 {
panic("no return value specified for CreateEntityHook")
}
var r0 *github.Hook
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *github.Hook) (*github.Hook, error)); ok {
return rf(ctx, hook)
}
if rf, ok := ret.Get(0).(func(context.Context, *github.Hook) *github.Hook); ok {
r0 = rf(ctx, hook)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *github.Hook) error); ok {
r1 = rf(ctx, hook)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateEntityRegistrationToken provides a mock function with given fields: ctx
func (_m *GithubEntityOperations) CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for CreateEntityRegistrationToken")
}
var r0 *github.RegistrationToken
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context) (*github.RegistrationToken, *github.Response, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) *github.RegistrationToken); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RegistrationToken)
}
}
if rf, ok := ret.Get(1).(func(context.Context) *github.Response); ok {
r1 = rf(ctx)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context) error); ok {
r2 = rf(ctx)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// DeleteEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubEntityOperations) DeleteEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for DeleteEntityHook")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubEntityOperations) GetEntityHook(ctx context.Context, id int64) (*github.Hook, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for GetEntityHook")
}
var r0 *github.Hook
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Hook, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Hook); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetEntityJITConfig provides a mock function with given fields: ctx, instance, pool, labels
func (_m *GithubEntityOperations) GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (map[string]string, *github.Runner, error) {
ret := _m.Called(ctx, instance, pool, labels)
if len(ret) == 0 {
panic("no return value specified for GetEntityJITConfig")
}
var r0 map[string]string
var r1 *github.Runner
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, params.Pool, []string) (map[string]string, *github.Runner, error)); ok {
return rf(ctx, instance, pool, labels)
}
if rf, ok := ret.Get(0).(func(context.Context, string, params.Pool, []string) map[string]string); ok {
r0 = rf(ctx, instance, pool, labels)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, params.Pool, []string) *github.Runner); ok {
r1 = rf(ctx, instance, pool, labels)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Runner)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, params.Pool, []string) error); ok {
r2 = rf(ctx, instance, pool, labels)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListEntityHooks provides a mock function with given fields: ctx, opts
func (_m *GithubEntityOperations) ListEntityHooks(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, opts)
if len(ret) == 0 {
panic("no return value specified for ListEntityHooks")
}
var r0 []*github.Hook
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) ([]*github.Hook, *github.Response, error)); ok {
return rf(ctx, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) []*github.Hook); ok {
r0 = rf(ctx, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.Hook)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, *github.ListOptions) error); ok {
r2 = rf(ctx, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListEntityRunnerApplicationDownloads provides a mock function with given fields: ctx
func (_m *GithubEntityOperations) ListEntityRunnerApplicationDownloads(ctx context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ListEntityRunnerApplicationDownloads")
}
var r0 []*github.RunnerApplicationDownload
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []*github.RunnerApplicationDownload); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.RunnerApplicationDownload)
}
}
if rf, ok := ret.Get(1).(func(context.Context) *github.Response); ok {
r1 = rf(ctx)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context) error); ok {
r2 = rf(ctx)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListEntityRunners provides a mock function with given fields: ctx, opts
func (_m *GithubEntityOperations) ListEntityRunners(ctx context.Context, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, opts)
if len(ret) == 0 {
panic("no return value specified for ListEntityRunners")
}
var r0 *github.Runners
var r1 *github.Response
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) (*github.Runners, *github.Response, error)); ok {
return rf(ctx, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, *github.ListOptions) *github.Runners); ok {
r0 = rf(ctx, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Runners)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
if rf, ok := ret.Get(2).(func(context.Context, *github.ListOptions) error); ok {
r2 = rf(ctx, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// PingEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubEntityOperations) PingEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for PingEntityHook")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, id)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveEntityRunner provides a mock function with given fields: ctx, runnerID
func (_m *GithubEntityOperations) RemoveEntityRunner(ctx context.Context, runnerID int64) (*github.Response, error) {
ret := _m.Called(ctx, runnerID)
if len(ret) == 0 {
panic("no return value specified for RemoveEntityRunner")
}
var r0 *github.Response
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*github.Response, error)); ok {
return rf(ctx, runnerID)
}
if rf, ok := ret.Get(0).(func(context.Context, int64) *github.Response); ok {
r0 = rf(ctx, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
r1 = rf(ctx, runnerID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewGithubEntityOperations creates a new instance of GithubEntityOperations. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewGithubEntityOperations(t interface {
mock.TestingT
Cleanup(func())
}) *GithubEntityOperations {
mock := &GithubEntityOperations{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -3,23 +3,21 @@ package common
import (
"context"
"github.com/cloudbase/garm/params"
"github.com/google/go-github/v57/github"
)
type OrganizationHooks interface {
ListOrgHooks(ctx context.Context, org string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error)
GetOrgHook(ctx context.Context, org string, id int64) (*github.Hook, *github.Response, error)
CreateOrgHook(ctx context.Context, org string, hook *github.Hook) (*github.Hook, *github.Response, error)
DeleteOrgHook(ctx context.Context, org string, id int64) (*github.Response, error)
PingOrgHook(ctx context.Context, org string, id int64) (*github.Response, error)
}
type RepositoryHooks interface {
ListRepoHooks(ctx context.Context, owner, repo string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error)
GetRepoHook(ctx context.Context, owner, repo string, id int64) (*github.Hook, *github.Response, error)
CreateRepoHook(ctx context.Context, owner, repo string, hook *github.Hook) (*github.Hook, *github.Response, error)
DeleteRepoHook(ctx context.Context, owner, repo string, id int64) (*github.Response, error)
PingRepoHook(ctx context.Context, owner, repo string, id int64) (*github.Response, error)
type GithubEntityOperations interface {
ListEntityHooks(ctx context.Context, opts *github.ListOptions) (ret []*github.Hook, response *github.Response, err error)
GetEntityHook(ctx context.Context, id int64) (ret *github.Hook, err error)
CreateEntityHook(ctx context.Context, hook *github.Hook) (ret *github.Hook, err error)
DeleteEntityHook(ctx context.Context, id int64) (ret *github.Response, err error)
PingEntityHook(ctx context.Context, id int64) (ret *github.Response, err error)
ListEntityRunners(ctx context.Context, opts *github.ListOptions) (*github.Runners, *github.Response, error)
ListEntityRunnerApplicationDownloads(ctx context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error)
RemoveEntityRunner(ctx context.Context, runnerID int64) (*github.Response, error)
CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error)
GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (jitConfigMap map[string]string, runner *github.Runner, err error)
}
// GithubClient that describes the minimum list of functions we need to interact with github.
@ -27,50 +25,8 @@ type RepositoryHooks interface {
//
//go:generate mockery --all
type GithubClient interface {
OrganizationHooks
RepositoryHooks
GithubEntityOperations
// GetWorkflowJobByID gets details about a single workflow job.
GetWorkflowJobByID(ctx context.Context, owner, repo string, jobID int64) (*github.WorkflowJob, *github.Response, error)
// ListRunners lists all runners within a repository.
ListRunners(ctx context.Context, owner, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error)
// ListRunnerApplicationDownloads returns a list of github runner application downloads for the
// various supported operating systems and architectures.
ListRunnerApplicationDownloads(ctx context.Context, owner, repo string) ([]*github.RunnerApplicationDownload, *github.Response, error)
// RemoveRunner removes one runner from a repository.
RemoveRunner(ctx context.Context, owner, repo string, runnerID int64) (*github.Response, error)
// CreateRegistrationToken creates a runner registration token for one repository.
CreateRegistrationToken(ctx context.Context, owner, repo string) (*github.RegistrationToken, *github.Response, error)
// GenerateRepoJITConfig generates a just-in-time configuration for a repository.
GenerateRepoJITConfig(ctx context.Context, owner, repo string, request *github.GenerateJITConfigRequest) (*github.JITRunnerConfig, *github.Response, error)
// ListOrganizationRunners lists all runners within an organization.
ListOrganizationRunners(ctx context.Context, owner string, opts *github.ListOptions) (*github.Runners, *github.Response, error)
// ListOrganizationRunnerApplicationDownloads returns a list of github runner application downloads for the
// various supported operating systems and architectures.
ListOrganizationRunnerApplicationDownloads(ctx context.Context, owner string) ([]*github.RunnerApplicationDownload, *github.Response, error)
// RemoveOrganizationRunner removes one github runner from an organization.
RemoveOrganizationRunner(ctx context.Context, owner string, runnerID int64) (*github.Response, error)
// CreateOrganizationRegistrationToken creates a runner registration token for an organization.
CreateOrganizationRegistrationToken(ctx context.Context, owner string) (*github.RegistrationToken, *github.Response, error)
// GenerateOrgJITConfig generate a just-in-time configuration for an organization.
GenerateOrgJITConfig(ctx context.Context, owner string, request *github.GenerateJITConfigRequest) (*github.JITRunnerConfig, *github.Response, error)
// ListOrganizationRunnerGroups lists all runner groups within an organization.
ListOrganizationRunnerGroups(ctx context.Context, org string, opts *github.ListOrgRunnerGroupOptions) (*github.RunnerGroups, *github.Response, error)
}
type GithubEnterpriseClient interface {
// ListRunners lists all runners within a repository.
ListRunners(ctx context.Context, enterprise string, opts *github.ListOptions) (*github.Runners, *github.Response, error)
// RemoveRunner removes one runner from an enterprise.
RemoveRunner(ctx context.Context, enterprise string, runnerID int64) (*github.Response, error)
// CreateRegistrationToken creates a runner registration token for an enterprise.
CreateRegistrationToken(ctx context.Context, enterprise string) (*github.RegistrationToken, *github.Response, error)
// ListRunnerApplicationDownloads returns a list of github runner application downloads for the
// various supported operating systems and architectures.
ListRunnerApplicationDownloads(ctx context.Context, enterprise string) ([]*github.RunnerApplicationDownload, *github.Response, error)
// GenerateEnterpriseJITConfig generate a just-in-time configuration for an enterprise.
GenerateEnterpriseJITConfig(ctx context.Context, enterprise string, request *github.GenerateJITConfigRequest) (*github.JITRunnerConfig, *github.Response, error)
// ListRunnerGroups lists all self-hosted runner groups configured in an enterprise.
ListRunnerGroups(ctx context.Context, enterprise string, opts *github.ListEnterpriseRunnerGroupOptions) (*github.EnterpriseRunnerGroups, *github.Response, error)
}

View file

@ -1,6 +1,8 @@
package pool
import (
"context"
"net/http"
"net/url"
"strings"
@ -62,3 +64,25 @@ func hookToParamsHookInfo(hook *github.Hook) params.HookInfo {
InsecureSSL: insecureSSL,
}
}
func (r *basePoolManager) listHooks(ctx context.Context) ([]*github.Hook, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allHooks []*github.Hook
for {
hooks, ghResp, err := r.ghcli.ListEntityHooks(ctx, &opts)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusNotFound {
return nil, runnerErrors.NewBadRequestError("repository not found or your PAT does not have access to manage webhooks")
}
return nil, errors.Wrap(err, "fetching hooks")
}
allHooks = append(allHooks, hooks...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allHooks, nil
}

View file

@ -2,21 +2,12 @@ package pool
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
"sync"
"github.com/google/go-github/v57/github"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
"github.com/cloudbase/garm/util"
@ -27,7 +18,13 @@ var _ poolHelper = &enterprise{}
func NewEnterprisePoolManager(ctx context.Context, cfg params.Enterprise, cfgInternal params.Internal, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
ctx = util.WithContext(ctx, slog.Any("pool_mgr", cfg.Name), slog.Any("pool_type", params.GithubEntityTypeEnterprise))
ghc, ghEnterpriseClient, err := util.GithubClient(ctx, cfgInternal.GithubCredentialsDetails)
entity := params.GithubEntity{
Owner: cfg.Name,
ID: cfg.ID,
WebhookSecret: cfg.WebhookSecret,
EntityType: params.GithubEntityTypeEnterprise,
}
ghc, err := util.GithubClient(ctx, entity, cfgInternal.GithubCredentialsDetails)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
@ -36,21 +33,16 @@ func NewEnterprisePoolManager(ctx context.Context, cfg params.Enterprise, cfgInt
keyMuxes := &keyMutex{}
helper := &enterprise{
cfg: cfg,
cfgInternal: cfgInternal,
ctx: ctx,
ghcli: ghc,
ghcEnterpriseCli: ghEnterpriseClient,
id: cfg.ID,
store: store,
ctx: ctx,
id: cfg.ID,
store: store,
}
repo := &basePoolManager{
ctx: ctx,
entity: params.GithubEntity{
Name: cfg.Name,
EntityType: params.GithubEntityTypeEnterprise,
},
ctx: ctx,
cfgInternal: cfgInternal,
ghcli: ghc,
entity: entity,
store: store,
providers: providers,
controllerID: cfgInternal.ControllerID,
@ -70,258 +62,15 @@ func NewEnterprisePoolManager(ctx context.Context, cfg params.Enterprise, cfgInt
}
type enterprise struct {
cfg params.Enterprise
cfgInternal params.Internal
ctx context.Context
ghcli common.GithubClient
ghcEnterpriseCli common.GithubEnterpriseClient
id string
store dbCommon.Store
mux sync.Mutex
}
func (e *enterprise) PoolBalancerType() params.PoolBalancerType {
if e.cfgInternal.PoolBalancerType == "" {
return params.PoolBalancerTypeRoundRobin
}
return e.cfgInternal.PoolBalancerType
}
func (e *enterprise) findRunnerGroupByName(name string) (*github.EnterpriseRunnerGroup, error) {
// nolint:golangci-lint,godox
// TODO(gabriel-samfira): implement caching
opts := github.ListEnterpriseRunnerGroupOptions{
ListOptions: github.ListOptions{
PerPage: 100,
},
}
for {
metrics.GithubOperationCount.WithLabelValues(
"ListOrganizationRunnerGroups", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
runnerGroups, ghResp, err := e.ghcEnterpriseCli.ListRunnerGroups(e.ctx, e.cfg.Name, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListOrganizationRunnerGroups", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
for _, runnerGroup := range runnerGroups.RunnerGroups {
if runnerGroup.Name != nil && *runnerGroup.Name == name {
return runnerGroup, nil
}
}
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return nil, errors.Wrap(runnerErrors.ErrNotFound, "runner group not found")
}
func (e *enterprise) GetJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (jitConfigMap map[string]string, runner *github.Runner, err error) {
var rg int64 = 1
if pool.GitHubRunnerGroup != "" {
runnerGroup, err := e.findRunnerGroupByName(pool.GitHubRunnerGroup)
if err != nil {
return nil, nil, fmt.Errorf("failed to find runner group: %w", err)
}
rg = *runnerGroup.ID
}
req := github.GenerateJITConfigRequest{
Name: instance,
RunnerGroupID: rg,
Labels: labels,
// nolint:golangci-lint,godox
// TODO(gabriel-samfira): Should we make this configurable?
WorkFolder: github.String("_work"),
}
metrics.GithubOperationCount.WithLabelValues(
"GenerateEnterpriseJITConfig", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
jitConfig, resp, err := e.ghcEnterpriseCli.GenerateEnterpriseJITConfig(ctx, e.cfg.Name, &req)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GenerateEnterpriseJITConfig", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
if resp != nil && resp.StatusCode == http.StatusUnauthorized {
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
runner = jitConfig.Runner
defer func() {
if err != nil && runner != nil {
metrics.GithubOperationCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
_, innerErr := e.ghcEnterpriseCli.RemoveRunner(e.ctx, e.cfg.Name, runner.GetID())
if innerErr != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
}
slog.With(slog.Any("error", innerErr)).ErrorContext(
ctx, "failed to remove runner",
"runner_id", runner.GetID(), "organization", e.cfg.Name)
}
}()
decoded, err := base64.StdEncoding.DecodeString(*jitConfig.EncodedJITConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to decode JIT config: %w", err)
}
var ret map[string]string
if err := json.Unmarshal(decoded, &ret); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal JIT config: %w", err)
}
return ret, jitConfig.Runner, nil
}
func (e *enterprise) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
if err := e.ValidateOwner(job); err != nil {
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
}
metrics.GithubOperationCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
workflow, ghResp, err := e.ghcli.GetWorkflowJobByID(e.ctx, job.Repository.Owner.Login, job.Repository.Name, job.WorkflowJob.ID)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return params.RunnerInfo{}, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching workflow info")
}
return params.RunnerInfo{}, errors.Wrap(err, "fetching workflow info")
}
if workflow.RunnerName != nil {
return params.RunnerInfo{
Name: *workflow.RunnerName,
Labels: workflow.Labels,
}, nil
}
return params.RunnerInfo{}, fmt.Errorf("failed to find runner name from workflow")
}
func (e *enterprise) UpdateState(param params.UpdatePoolStateParams) error {
e.mux.Lock()
defer e.mux.Unlock()
e.cfg.WebhookSecret = param.WebhookSecret
if param.InternalConfig != nil {
e.cfgInternal = *param.InternalConfig
}
ghc, ghcEnterprise, err := util.GithubClient(e.ctx, e.cfgInternal.GithubCredentialsDetails)
if err != nil {
return errors.Wrap(err, "getting github client")
}
e.ghcli = ghc
e.ghcEnterpriseCli = ghcEnterprise
return nil
}
func (e *enterprise) GetGithubRunners() ([]*github.Runner, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allRunners []*github.Runner
for {
metrics.GithubOperationCount.WithLabelValues(
"ListRunners", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
runners, ghResp, err := e.ghcEnterpriseCli.ListRunners(e.ctx, e.cfg.Name, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListRunners", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (e *enterprise) FetchTools() ([]commonParams.RunnerApplicationDownload, error) {
e.mux.Lock()
defer e.mux.Unlock()
metrics.GithubOperationCount.WithLabelValues(
"ListRunnerApplicationDownloads", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
tools, ghResp, err := e.ghcEnterpriseCli.ListRunnerApplicationDownloads(e.ctx, e.cfg.Name)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListRunnerApplicationDownloads", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runner tools")
}
ret := []commonParams.RunnerApplicationDownload{}
for _, tool := range tools {
if tool == nil {
continue
}
ret = append(ret, commonParams.RunnerApplicationDownload(*tool))
}
return ret, nil
ctx context.Context
id string
store dbCommon.Store
}
func (e *enterprise) FetchDbInstances() ([]params.Instance, error) {
return e.store.ListEnterpriseInstances(e.ctx, e.id)
}
func (e *enterprise) RemoveGithubRunner(runnerID int64) (*github.Response, error) {
metrics.GithubOperationCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
ghResp, err := e.ghcEnterpriseCli.RemoveRunner(e.ctx, e.cfg.Name, runnerID)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
return nil, err
}
return ghResp, nil
}
func (e *enterprise) ListPools() ([]params.Pool, error) {
pools, err := e.store.ListEnterprisePools(e.ctx, e.id)
if err != nil {
@ -330,42 +79,6 @@ func (e *enterprise) ListPools() ([]params.Pool, error) {
return pools, nil
}
func (e *enterprise) GithubURL() string {
return fmt.Sprintf("%s/enterprises/%s", e.cfgInternal.GithubCredentialsDetails.BaseURL, e.cfg.Name)
}
func (e *enterprise) JwtToken() string {
return e.cfgInternal.JWTSecret
}
func (e *enterprise) GetGithubRegistrationToken() (string, error) {
metrics.GithubOperationCount.WithLabelValues(
"CreateRegistrationToken", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
tk, ghResp, err := e.ghcEnterpriseCli.CreateRegistrationToken(e.ctx, e.cfg.Name)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"CreateRegistrationToken", // label: operation
params.MetricsLabelEnterpriseScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching registration token")
}
return "", errors.Wrap(err, "creating runner token")
}
return *tk.Token, nil
}
func (e *enterprise) String() string {
return e.cfg.Name
}
func (e *enterprise) WebhookSecret() string {
return e.cfg.WebhookSecret
}
func (e *enterprise) GetPoolByID(poolID string) (params.Pool, error) {
pool, err := e.store.GetEnterprisePool(e.ctx, e.id, poolID)
if err != nil {
@ -373,26 +86,3 @@ func (e *enterprise) GetPoolByID(poolID string) (params.Pool, error) {
}
return pool, nil
}
func (e *enterprise) ValidateOwner(job params.WorkflowJob) error {
if !strings.EqualFold(job.Enterprise.Slug, e.cfg.Name) {
return runnerErrors.NewBadRequestError("job not meant for this pool manager")
}
return nil
}
func (e *enterprise) ID() string {
return e.id
}
func (e *enterprise) InstallHook(_ context.Context, _ *github.Hook) (params.HookInfo, error) {
return params.HookInfo{}, fmt.Errorf("not implemented")
}
func (e *enterprise) UninstallHook(_ context.Context, _ string) error {
return fmt.Errorf("not implemented")
}
func (e *enterprise) GetHookInfo(_ context.Context) (params.HookInfo, error) {
return params.HookInfo{}, fmt.Errorf("not implemented")
}

View file

@ -15,36 +15,11 @@
package pool
import (
"context"
"github.com/google/go-github/v57/github"
commonParams "github.com/cloudbase/garm-provider-common/params"
"github.com/cloudbase/garm/params"
)
type poolHelper interface {
GetGithubRunners() ([]*github.Runner, error)
GetGithubRegistrationToken() (string, error)
GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error)
RemoveGithubRunner(runnerID int64) (*github.Response, error)
FetchTools() ([]commonParams.RunnerApplicationDownload, error)
InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error)
UninstallHook(ctx context.Context, url string) error
GetHookInfo(ctx context.Context) (params.HookInfo, error)
GetJITConfig(ctx context.Context, instanceName string, pool params.Pool, labels []string) (map[string]string, *github.Runner, error)
FetchDbInstances() ([]params.Instance, error)
ListPools() ([]params.Pool, error)
GithubURL() string
JwtToken() string
String() string
GetPoolByID(poolID string) (params.Pool, error)
ValidateOwner(job params.WorkflowJob) error
UpdateState(param params.UpdatePoolStateParams) error
WebhookSecret() string
ID() string
PoolBalancerType() params.PoolBalancerType
}

View file

@ -16,21 +16,12 @@ package pool
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
"sync"
"github.com/google/go-github/v57/github"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
"github.com/cloudbase/garm/util"
@ -41,7 +32,13 @@ var _ poolHelper = &organization{}
func NewOrganizationPoolManager(ctx context.Context, cfg params.Organization, cfgInternal params.Internal, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
ctx = util.WithContext(ctx, slog.Any("pool_mgr", cfg.Name), slog.Any("pool_type", params.GithubEntityTypeOrganization))
ghc, _, err := util.GithubClient(ctx, cfgInternal.GithubCredentialsDetails)
entity := params.GithubEntity{
Owner: cfg.Name,
ID: cfg.ID,
WebhookSecret: cfg.WebhookSecret,
EntityType: params.GithubEntityTypeOrganization,
}
ghc, err := util.GithubClient(ctx, entity, cfgInternal.GithubCredentialsDetails)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
@ -50,20 +47,16 @@ func NewOrganizationPoolManager(ctx context.Context, cfg params.Organization, cf
keyMuxes := &keyMutex{}
helper := &organization{
cfg: cfg,
cfgInternal: cfgInternal,
ctx: ctx,
ghcli: ghc,
id: cfg.ID,
store: store,
ctx: ctx,
id: cfg.ID,
store: store,
}
repo := &basePoolManager{
ctx: ctx,
entity: params.GithubEntity{
Name: cfg.Name,
EntityType: params.GithubEntityTypeOrganization,
},
ctx: ctx,
cfgInternal: cfgInternal,
ghcli: ghc,
entity: entity,
store: store,
providers: providers,
controllerID: cfgInternal.ControllerID,
@ -83,259 +76,15 @@ func NewOrganizationPoolManager(ctx context.Context, cfg params.Organization, cf
}
type organization struct {
cfg params.Organization
cfgInternal params.Internal
ctx context.Context
ghcli common.GithubClient
id string
store dbCommon.Store
mux sync.Mutex
}
func (o *organization) PoolBalancerType() params.PoolBalancerType {
if o.cfgInternal.PoolBalancerType == "" {
return params.PoolBalancerTypeRoundRobin
}
return o.cfgInternal.PoolBalancerType
}
func (o *organization) findRunnerGroupByName(name string) (*github.RunnerGroup, error) {
// nolint:golangci-lint,godox
// TODO(gabriel-samfira): implement caching
opts := github.ListOrgRunnerGroupOptions{
ListOptions: github.ListOptions{
PerPage: 100,
},
}
for {
metrics.GithubOperationCount.WithLabelValues(
"ListOrganizationRunnerGroups", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
runnerGroups, ghResp, err := o.ghcli.ListOrganizationRunnerGroups(o.ctx, o.cfg.Name, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListOrganizationRunnerGroups", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
for _, runnerGroup := range runnerGroups.RunnerGroups {
if runnerGroup.GetName() == name {
return runnerGroup, nil
}
}
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return nil, errors.Wrap(runnerErrors.ErrNotFound, "runner group not found")
}
func (o *organization) GetJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (jitConfigMap map[string]string, runner *github.Runner, err error) {
var rg int64 = 1
if pool.GitHubRunnerGroup != "" {
runnerGroup, err := o.findRunnerGroupByName(pool.GitHubRunnerGroup)
if err != nil {
return nil, nil, fmt.Errorf("failed to find runner group: %w", err)
}
rg = runnerGroup.GetID()
}
req := github.GenerateJITConfigRequest{
Name: instance,
RunnerGroupID: rg,
Labels: labels,
// nolint:golangci-lint,godox
// TODO(gabriel-samfira): Should we make this configurable?
WorkFolder: github.String("_work"),
}
metrics.GithubOperationCount.WithLabelValues(
"GenerateOrgJITConfig", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
jitConfig, resp, err := o.ghcli.GenerateOrgJITConfig(ctx, o.cfg.Name, &req)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GenerateOrgJITConfig", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if resp != nil && resp.StatusCode == http.StatusUnauthorized {
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
runner = jitConfig.GetRunner()
defer func() {
if err != nil && runner != nil {
metrics.GithubOperationCount.WithLabelValues(
"RemoveOrganizationRunner", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
_, innerErr := o.ghcli.RemoveOrganizationRunner(o.ctx, o.cfg.Name, runner.GetID())
if innerErr != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"RemoveOrganizationRunner", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
}
slog.With(slog.Any("error", innerErr)).ErrorContext(
ctx, "failed to remove runner",
"runner_id", runner.GetID(), "organization", o.cfg.Name)
}
}()
decoded, err := base64.StdEncoding.DecodeString(jitConfig.GetEncodedJITConfig())
if err != nil {
return nil, nil, fmt.Errorf("failed to decode JIT config: %w", err)
}
var ret map[string]string
if err := json.Unmarshal(decoded, &ret); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal JIT config: %w", err)
}
return ret, runner, nil
}
func (o *organization) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
if err := o.ValidateOwner(job); err != nil {
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
}
metrics.GithubOperationCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
workflow, ghResp, err := o.ghcli.GetWorkflowJobByID(o.ctx, job.Organization.Login, job.Repository.Name, job.WorkflowJob.ID)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return params.RunnerInfo{}, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching workflow info")
}
return params.RunnerInfo{}, errors.Wrap(err, "fetching workflow info")
}
if workflow.RunnerName != nil {
return params.RunnerInfo{
Name: *workflow.RunnerName,
Labels: workflow.Labels,
}, nil
}
return params.RunnerInfo{}, fmt.Errorf("failed to find runner name from workflow")
}
func (o *organization) UpdateState(param params.UpdatePoolStateParams) error {
o.mux.Lock()
defer o.mux.Unlock()
o.cfg.WebhookSecret = param.WebhookSecret
if param.InternalConfig != nil {
o.cfgInternal = *param.InternalConfig
}
ghc, _, err := util.GithubClient(o.ctx, o.cfgInternal.GithubCredentialsDetails)
if err != nil {
return errors.Wrap(err, "getting github client")
}
o.ghcli = ghc
return nil
}
func (o *organization) GetGithubRunners() ([]*github.Runner, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allRunners []*github.Runner
for {
metrics.GithubOperationCount.WithLabelValues(
"ListOrganizationRunners", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
runners, ghResp, err := o.ghcli.ListOrganizationRunners(o.ctx, o.cfg.Name, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListOrganizationRunners", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (o *organization) FetchTools() ([]commonParams.RunnerApplicationDownload, error) {
o.mux.Lock()
defer o.mux.Unlock()
metrics.GithubOperationCount.WithLabelValues(
"ListOrganizationRunnerApplicationDownloads", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
tools, ghResp, err := o.ghcli.ListOrganizationRunnerApplicationDownloads(o.ctx, o.cfg.Name)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListOrganizationRunnerApplicationDownloads", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching tools")
}
return nil, errors.Wrap(err, "fetching runner tools")
}
ret := []commonParams.RunnerApplicationDownload{}
for _, tool := range tools {
if tool == nil {
continue
}
ret = append(ret, commonParams.RunnerApplicationDownload(*tool))
}
return ret, nil
ctx context.Context
id string
store dbCommon.Store
}
func (o *organization) FetchDbInstances() ([]params.Instance, error) {
return o.store.ListOrgInstances(o.ctx, o.id)
}
func (o *organization) RemoveGithubRunner(runnerID int64) (*github.Response, error) {
metrics.GithubOperationCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
ghResp, err := o.ghcli.RemoveOrganizationRunner(o.ctx, o.cfg.Name, runnerID)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
return nil, err
}
return ghResp, nil
}
func (o *organization) ListPools() ([]params.Pool, error) {
pools, err := o.store.ListOrgPools(o.ctx, o.id)
if err != nil {
@ -344,42 +93,6 @@ func (o *organization) ListPools() ([]params.Pool, error) {
return pools, nil
}
func (o *organization) GithubURL() string {
return fmt.Sprintf("%s/%s", o.cfgInternal.GithubCredentialsDetails.BaseURL, o.cfg.Name)
}
func (o *organization) JwtToken() string {
return o.cfgInternal.JWTSecret
}
func (o *organization) GetGithubRegistrationToken() (string, error) {
metrics.GithubOperationCount.WithLabelValues(
"CreateOrganizationRegistrationToken", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
tk, ghResp, err := o.ghcli.CreateOrganizationRegistrationToken(o.ctx, o.cfg.Name)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"CreateOrganizationRegistrationToken", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching token")
}
return "", errors.Wrap(err, "creating runner token")
}
return *tk.Token, nil
}
func (o *organization) String() string {
return o.cfg.Name
}
func (o *organization) WebhookSecret() string {
return o.cfg.WebhookSecret
}
func (o *organization) GetPoolByID(poolID string) (params.Pool, error) {
pool, err := o.store.GetOrganizationPool(o.ctx, o.id, poolID)
if err != nil {
@ -387,126 +100,3 @@ func (o *organization) GetPoolByID(poolID string) (params.Pool, error) {
}
return pool, nil
}
func (o *organization) ValidateOwner(job params.WorkflowJob) error {
if !strings.EqualFold(job.Organization.Login, o.cfg.Name) {
return runnerErrors.NewBadRequestError("job not meant for this pool manager")
}
return nil
}
func (o *organization) ID() string {
return o.id
}
func (o *organization) listHooks(ctx context.Context) ([]*github.Hook, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allHooks []*github.Hook
for {
metrics.GithubOperationCount.WithLabelValues(
"ListOrgHooks", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
hooks, ghResp, err := o.ghcli.ListOrgHooks(ctx, o.cfg.Name, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListOrgHooks", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusNotFound {
return nil, runnerErrors.NewBadRequestError("organization not found or your PAT does not have access to manage webhooks")
}
return nil, errors.Wrap(err, "fetching hooks")
}
allHooks = append(allHooks, hooks...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allHooks, nil
}
func (o *organization) InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) {
allHooks, err := o.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
}
if err := validateHookRequest(o.cfgInternal.ControllerID, o.cfgInternal.BaseWebhookURL, allHooks, req); err != nil {
return params.HookInfo{}, errors.Wrap(err, "validating hook request")
}
metrics.GithubOperationCount.WithLabelValues(
"CreateOrgHook", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
hook, _, err := o.ghcli.CreateOrgHook(ctx, o.cfg.Name, req)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"CreateOrgHook", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
return params.HookInfo{}, errors.Wrap(err, "creating organization hook")
}
metrics.GithubOperationCount.WithLabelValues(
"PingOrgHook", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
if _, err := o.ghcli.PingOrgHook(ctx, o.cfg.Name, hook.GetID()); err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"PingOrgHook", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to ping hook", "hook_id", hook.GetID())
}
return hookToParamsHookInfo(hook), nil
}
func (o *organization) UninstallHook(ctx context.Context, url string) error {
allHooks, err := o.listHooks(ctx)
if err != nil {
return errors.Wrap(err, "listing hooks")
}
for _, hook := range allHooks {
if hook.Config["url"] == url {
metrics.GithubOperationCount.WithLabelValues(
"DeleteOrgHook", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
_, err = o.ghcli.DeleteOrgHook(ctx, o.cfg.Name, hook.GetID())
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"DeleteOrgHook", // label: operation
params.MetricsLabelOrganizationScope, // label: scope
).Inc()
return errors.Wrap(err, "deleting hook")
}
return nil
}
}
return nil
}
func (o *organization) GetHookInfo(ctx context.Context) (params.HookInfo, error) {
allHooks, err := o.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
}
for _, hook := range allHooks {
hookInfo := hookToParamsHookInfo(hook)
if strings.EqualFold(hookInfo.URL, o.cfgInternal.ControllerWebhookURL) {
return hookInfo, nil
}
}
return params.HookInfo{}, runnerErrors.NewNotFoundError("hook not found")
}

View file

@ -35,8 +35,10 @@ import (
"github.com/cloudbase/garm-provider-common/util"
"github.com/cloudbase/garm/auth"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
garmUtil "github.com/cloudbase/garm/util"
)
var (
@ -96,6 +98,8 @@ type basePoolManager struct {
ctx context.Context
controllerID string
entity params.GithubEntity
ghcli common.GithubClient
cfgInternal params.Internal
store dbCommon.Store
@ -117,7 +121,7 @@ type basePoolManager struct {
}
func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
if err := r.helper.ValidateOwner(job); err != nil {
if err := r.ValidateOwner(job); err != nil {
return errors.Wrap(err, "validating owner")
}
@ -142,7 +146,7 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
return
}
// This job is new to us. Check if we have a pool that can handle it.
potentialPools, err := r.store.FindPoolsMatchingAllTags(r.ctx, r.entity.EntityType, r.helper.ID(), jobParams.Labels)
potentialPools, err := r.store.FindPoolsMatchingAllTags(r.ctx, r.entity.EntityType, r.entity.ID, jobParams.Labels)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to find pools matching tags; not recording job",
@ -247,7 +251,7 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
// A runner has picked up the job, and is now running it. It may need to be replaced if the pool has
// a minimum number of idle runners configured.
pool, err := r.store.GetPoolByID(r.ctx, instance.PoolID)
pool, err := r.helper.GetPoolByID(instance.PoolID)
if err != nil {
return errors.Wrap(err, "getting pool")
}
@ -329,12 +333,12 @@ func (r *basePoolManager) startLoopForFunction(f func() error, interval time.Dur
func (r *basePoolManager) updateTools() error {
// Update tools cache.
tools, err := r.helper.FetchTools()
tools, err := r.FetchTools()
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to update tools for repo")
r.setPoolRunningState(false, err.Error())
return fmt.Errorf("failed to update tools for repo %s: %w", r.helper.String(), err)
return fmt.Errorf("failed to update tools for repo %s: %w", r.entity.String(), err)
}
r.mux.Lock()
r.tools = tools
@ -419,7 +423,7 @@ func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runne
continue
}
pool, err := r.store.GetPoolByID(r.ctx, instance.PoolID)
pool, err := r.helper.GetPoolByID(instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching instance pool info")
}
@ -489,7 +493,7 @@ func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
}
defer r.keyMux.Unlock(instance.Name, false)
pool, err := r.store.GetPoolByID(r.ctx, instance.PoolID)
pool, err := r.helper.GetPoolByID(instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching instance pool info")
}
@ -557,7 +561,7 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
slog.InfoContext(
r.ctx, "Runner has no database entry in garm, removing from github",
"runner_name", runner.GetName())
resp, err := r.helper.RemoveGithubRunner(*runner.ID)
resp, err := r.RemoveGithubRunner(*runner.ID)
if err != nil {
// Removed in the meantime?
if resp != nil && resp.StatusCode == http.StatusNotFound {
@ -637,7 +641,7 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
slog.InfoContext(
r.ctx, "Runner instance is no longer on the provider, removing from github",
"runner_name", dbInstance.Name)
resp, err := r.helper.RemoveGithubRunner(*runner.ID)
resp, err := r.RemoveGithubRunner(*runner.ID)
if err != nil {
// Removed in the meantime?
if resp != nil && resp.StatusCode == http.StatusNotFound {
@ -775,7 +779,7 @@ func (r *basePoolManager) AddRunner(ctx context.Context, poolID string, aditiona
if !provider.DisableJITConfig() {
// Attempt to create JIT config
jitConfig, runner, err = r.helper.GetJITConfig(ctx, name, pool, labels)
jitConfig, runner, err = r.ghcli.GetEntityJITConfig(ctx, name, pool, labels)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get JIT config, falling back to registration token")
@ -816,7 +820,7 @@ func (r *basePoolManager) AddRunner(ctx context.Context, poolID string, aditiona
}
if runner != nil {
_, runnerCleanupErr := r.helper.RemoveGithubRunner(runner.GetID())
_, runnerCleanupErr := r.RemoveGithubRunner(runner.GetID())
if err != nil {
slog.With(slog.Any("error", runnerCleanupErr)).ErrorContext(
ctx, "failed to remove runner",
@ -878,8 +882,8 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
jwtValidity := pool.RunnerTimeout()
entity := r.helper.String()
jwtToken, err := auth.NewInstanceJWTToken(instance, r.helper.JwtToken(), entity, pool.PoolType(), jwtValidity)
entity := r.entity.String()
jwtToken, err := auth.NewInstanceJWTToken(instance, r.JwtToken(), entity, pool.PoolType(), jwtValidity)
if err != nil {
return errors.Wrap(err, "fetching instance jwt token")
}
@ -889,7 +893,7 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
bootstrapArgs := commonParams.BootstrapInstance{
Name: instance.Name,
Tools: r.tools,
RepoURL: r.helper.GithubURL(),
RepoURL: r.GithubURL(),
MetadataURL: instance.MetadataURL,
CallbackURL: instance.CallbackURL,
InstanceToken: jwtToken,
@ -962,7 +966,7 @@ func (r *basePoolManager) getRunnerDetailsFromJob(job params.WorkflowJob) (param
slog.InfoContext(
r.ctx, "runner name not found in workflow job, attempting to fetch from API",
"job_id", job.WorkflowJob.ID)
runnerInfo, err = r.helper.GetRunnerInfoFromWorkflow(job)
runnerInfo, err = r.GetRunnerInfoFromWorkflow(job)
if err != nil {
return params.RunnerInfo{}, errors.Wrap(err, "fetching runner name from API")
}
@ -1144,7 +1148,7 @@ func (r *basePoolManager) scaleDownOnePool(ctx context.Context, pool params.Pool
// nolint:golangci-lint,godox
// TODO: should probably allow aditional filters to list functions. Would help to filter by date
// instead of returning a bunch of results and filtering manually.
queued, err := r.store.ListEntityJobsByStatus(r.ctx, r.entity.EntityType, r.helper.ID(), params.JobStatusQueued)
queued, err := r.store.ListEntityJobsByStatus(r.ctx, r.entity.EntityType, r.entity.ID, params.JobStatusQueued)
if err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "listing queued jobs")
}
@ -1584,7 +1588,7 @@ func (r *basePoolManager) Wait() error {
func (r *basePoolManager) runnerCleanup() (err error) {
slog.DebugContext(
r.ctx, "running runner cleanup")
runners, err := r.helper.GetGithubRunners()
runners, err := r.GetGithubRunners()
if err != nil {
return fmt.Errorf("failed to fetch github runners: %w", err)
}
@ -1650,9 +1654,27 @@ func (r *basePoolManager) Stop() error {
}
func (r *basePoolManager) RefreshState(param params.UpdatePoolStateParams) error {
if err := r.helper.UpdateState(param); err != nil {
return fmt.Errorf("failed to update pool state: %w", err)
r.mux.Lock()
r.entity.WebhookSecret = param.WebhookSecret
if param.InternalConfig != nil {
r.cfgInternal = *param.InternalConfig
}
r.urls = urls{
webhookURL: r.cfgInternal.BaseWebhookURL,
callbackURL: r.cfgInternal.InstanceCallbackURL,
metadataURL: r.cfgInternal.InstanceMetadataURL,
controllerWebhookURL: r.cfgInternal.ControllerWebhookURL,
}
ghc, err := garmUtil.GithubClient(r.ctx, r.entity, r.cfgInternal.GithubCredentialsDetails)
if err != nil {
return errors.Wrap(err, "getting github client")
}
r.ghcli = ghc
r.mux.Unlock()
// Update the tools as soon as state is updated. This should revive a stopped pool manager
// or stop one if the supplied credentials are not okay.
if err := r.updateTools(); err != nil {
@ -1662,25 +1684,25 @@ func (r *basePoolManager) RefreshState(param params.UpdatePoolStateParams) error
}
func (r *basePoolManager) WebhookSecret() string {
return r.helper.WebhookSecret()
return r.entity.WebhookSecret
}
func (r *basePoolManager) GithubRunnerRegistrationToken() (string, error) {
return r.helper.GetGithubRegistrationToken()
return r.GetGithubRegistrationToken()
}
func (r *basePoolManager) ID() string {
return r.helper.ID()
return r.entity.ID
}
// Delete runner will delete a runner from a pool. If forceRemove is set to true, any error received from
// the IaaS provider will be ignored and deletion will continue.
func (r *basePoolManager) DeleteRunner(runner params.Instance, forceRemove, bypassGHUnauthorizedError bool) error {
if !r.managerIsRunning && !bypassGHUnauthorizedError {
return runnerErrors.NewConflictError("pool manager is not running for %s", r.helper.String())
return runnerErrors.NewConflictError("pool manager is not running for %s", r.entity.String())
}
if runner.AgentID != 0 {
resp, err := r.helper.RemoveGithubRunner(runner.AgentID)
resp, err := r.RemoveGithubRunner(runner.AgentID)
if err != nil {
if resp != nil {
switch resp.StatusCode {
@ -1764,13 +1786,13 @@ func (r *basePoolManager) DeleteRunner(runner params.Instance, forceRemove, bypa
// so those will trigger the creation of a runner. The jobs we don't know about will be dealt with by the idle runners.
// Once jobs are consumed, you can set min-idle-runners to 0 again.
func (r *basePoolManager) consumeQueuedJobs() error {
queued, err := r.store.ListEntityJobsByStatus(r.ctx, r.entity.EntityType, r.helper.ID(), params.JobStatusQueued)
queued, err := r.store.ListEntityJobsByStatus(r.ctx, r.entity.EntityType, r.entity.ID, params.JobStatusQueued)
if err != nil {
return errors.Wrap(err, "listing queued jobs")
}
poolsCache := poolsForTags{
poolCacheType: r.helper.PoolBalancerType(),
poolCacheType: r.PoolBalancerType(),
}
slog.DebugContext(
@ -1822,7 +1844,7 @@ func (r *basePoolManager) consumeQueuedJobs() error {
poolRR, ok := poolsCache.Get(job.Labels)
if !ok {
potentialPools, err := r.store.FindPoolsMatchingAllTags(r.ctx, r.entity.EntityType, r.helper.ID(), job.Labels)
potentialPools, err := r.store.FindPoolsMatchingAllTags(r.ctx, r.entity.EntityType, r.entity.ID, job.Labels)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "error finding pools matching labels")
@ -1893,6 +1915,49 @@ func (r *basePoolManager) consumeQueuedJobs() error {
return nil
}
func (r *basePoolManager) UninstallHook(ctx context.Context, url string) error {
allHooks, err := r.listHooks(ctx)
if err != nil {
return errors.Wrap(err, "listing hooks")
}
for _, hook := range allHooks {
if hook.Config["url"] == url {
_, err = r.ghcli.DeleteEntityHook(ctx, hook.GetID())
if err != nil {
return fmt.Errorf("deleting hook: %w", err)
}
return nil
}
}
return nil
}
func (r *basePoolManager) InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) {
allHooks, err := r.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
}
if err := validateHookRequest(r.cfgInternal.ControllerID, r.cfgInternal.BaseWebhookURL, allHooks, req); err != nil {
return params.HookInfo{}, errors.Wrap(err, "validating hook request")
}
hook, err := r.ghcli.CreateEntityHook(ctx, req)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "creating entity hook")
}
if _, err := r.ghcli.PingEntityHook(ctx, hook.GetID()); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to ping hook",
"hook_id", hook.GetID(),
"entity", r.entity)
}
return hookToParamsHookInfo(hook), nil
}
func (r *basePoolManager) InstallWebhook(ctx context.Context, param params.InstallWebhookParams) (params.HookInfo, error) {
if r.urls.controllerWebhookURL == "" {
return params.HookInfo{}, errors.Wrap(runnerErrors.ErrBadRequest, "controller webhook url is empty")
@ -1915,7 +1980,158 @@ func (r *basePoolManager) InstallWebhook(ctx context.Context, param params.Insta
},
}
return r.helper.InstallHook(ctx, req)
return r.InstallHook(ctx, req)
}
func (r *basePoolManager) GetHookInfo(ctx context.Context) (params.HookInfo, error) {
allHooks, err := r.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
}
for _, hook := range allHooks {
hookInfo := hookToParamsHookInfo(hook)
if strings.EqualFold(hookInfo.URL, r.urls.webhookURL) {
return hookInfo, nil
}
}
return params.HookInfo{}, runnerErrors.NewNotFoundError("hook not found")
}
func (r *basePoolManager) ValidateOwner(job params.WorkflowJob) error {
switch r.entity.EntityType {
case params.GithubEntityTypeRepository:
if !strings.EqualFold(job.Repository.Name, r.entity.Name) || !strings.EqualFold(job.Repository.Owner.Login, r.entity.Owner) {
return runnerErrors.NewBadRequestError("job not meant for this pool manager")
}
case params.GithubEntityTypeOrganization:
if !strings.EqualFold(job.Organization.Login, r.entity.Owner) {
return runnerErrors.NewBadRequestError("job not meant for this pool manager")
}
case params.GithubEntityTypeEnterprise:
if !strings.EqualFold(job.Enterprise.Slug, r.entity.Owner) {
return runnerErrors.NewBadRequestError("job not meant for this pool manager")
}
default:
return runnerErrors.NewBadRequestError("unknown entity type")
}
return nil
}
func (r *basePoolManager) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
if err := r.ValidateOwner(job); err != nil {
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
}
metrics.GithubOperationCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
r.entity.LabelScope(), // label: scope
).Inc()
workflow, ghResp, err := r.ghcli.GetWorkflowJobByID(r.ctx, job.Repository.Owner.Login, job.Repository.Name, job.WorkflowJob.ID)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
r.entity.LabelScope(), // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return params.RunnerInfo{}, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching workflow info")
}
return params.RunnerInfo{}, errors.Wrap(err, "fetching workflow info")
}
if workflow.RunnerName != nil {
return params.RunnerInfo{
Name: *workflow.RunnerName,
Labels: workflow.Labels,
}, nil
}
return params.RunnerInfo{}, fmt.Errorf("failed to find runner name from workflow")
}
func (r *basePoolManager) GetGithubRegistrationToken() (string, error) {
tk, ghResp, err := r.ghcli.CreateEntityRegistrationToken(r.ctx)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching token")
}
return "", errors.Wrap(err, "creating runner token")
}
return *tk.Token, nil
}
func (r *basePoolManager) RemoveGithubRunner(runnerID int64) (*github.Response, error) {
ghResp, err := r.ghcli.RemoveEntityRunner(r.ctx, runnerID)
if err != nil {
return nil, fmt.Errorf("removing runner: %w", err)
}
return ghResp, nil
}
func (r *basePoolManager) FetchTools() ([]commonParams.RunnerApplicationDownload, error) {
tools, ghResp, err := r.ghcli.ListEntityRunnerApplicationDownloads(r.ctx)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching tools")
}
return nil, errors.Wrap(err, "fetching runner tools")
}
ret := []commonParams.RunnerApplicationDownload{}
for _, tool := range tools {
if tool == nil {
continue
}
ret = append(ret, commonParams.RunnerApplicationDownload(*tool))
}
return ret, nil
}
func (r *basePoolManager) GetGithubRunners() ([]*github.Runner, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allRunners []*github.Runner
for {
runners, ghResp, err := r.ghcli.ListEntityRunners(r.ctx, &opts)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (r *basePoolManager) PoolBalancerType() params.PoolBalancerType {
if r.cfgInternal.PoolBalancerType == "" {
return params.PoolBalancerTypeRoundRobin
}
return r.cfgInternal.PoolBalancerType
}
func (r *basePoolManager) JwtToken() string {
return r.cfgInternal.JWTSecret
}
func (r *basePoolManager) GithubURL() string {
switch r.entity.EntityType {
case params.GithubEntityTypeRepository:
return fmt.Sprintf("%s/%s/%s", r.cfgInternal.GithubCredentialsDetails.BaseURL, r.entity.Owner, r.entity.Name)
case params.GithubEntityTypeOrganization:
return fmt.Sprintf("%s/%s", r.cfgInternal.GithubCredentialsDetails.BaseURL, r.entity.Owner)
case params.GithubEntityTypeEnterprise:
return fmt.Sprintf("%s/enterprises/%s", r.cfgInternal.GithubCredentialsDetails.BaseURL, r.entity.Owner)
}
return ""
}
func (r *basePoolManager) UninstallWebhook(ctx context.Context) error {
@ -1923,11 +2139,11 @@ func (r *basePoolManager) UninstallWebhook(ctx context.Context) error {
return errors.Wrap(runnerErrors.ErrBadRequest, "controller webhook url is empty")
}
return r.helper.UninstallHook(ctx, r.urls.controllerWebhookURL)
return r.UninstallHook(ctx, r.urls.controllerWebhookURL)
}
func (r *basePoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, error) {
return r.helper.GetHookInfo(ctx)
return r.GetHookInfo(ctx)
}
func (r *basePoolManager) RootCABundle() (params.CertificateBundle, error) {

View file

@ -16,21 +16,13 @@ package pool
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
"sync"
"github.com/google/go-github/v57/github"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params"
dbCommon "github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
"github.com/cloudbase/garm/util"
@ -41,7 +33,14 @@ var _ poolHelper = &repository{}
func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, cfgInternal params.Internal, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
ctx = util.WithContext(ctx, slog.Any("pool_mgr", fmt.Sprintf("%s/%s", cfg.Owner, cfg.Name)), slog.Any("pool_type", params.GithubEntityTypeRepository))
ghc, _, err := util.GithubClient(ctx, cfgInternal.GithubCredentialsDetails)
entity := params.GithubEntity{
Name: cfg.Name,
Owner: cfg.Owner,
ID: cfg.ID,
WebhookSecret: cfg.WebhookSecret,
EntityType: params.GithubEntityTypeRepository,
}
ghc, err := util.GithubClient(ctx, entity, cfgInternal.GithubCredentialsDetails)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
@ -50,21 +49,17 @@ func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, cfgInt
keyMuxes := &keyMutex{}
helper := &repository{
cfg: cfg,
cfgInternal: cfgInternal,
ctx: ctx,
ghcli: ghc,
id: cfg.ID,
store: store,
ctx: ctx,
id: cfg.ID,
store: store,
}
repo := &basePoolManager{
ctx: ctx,
entity: params.GithubEntity{
Name: cfg.Name,
Owner: cfg.Owner,
EntityType: params.GithubEntityTypeRepository,
},
ctx: ctx,
cfgInternal: cfgInternal,
entity: entity,
ghcli: ghc,
store: store,
providers: providers,
controllerID: cfgInternal.ControllerID,
@ -86,215 +81,15 @@ func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, cfgInt
var _ poolHelper = &repository{}
type repository struct {
cfg params.Repository
cfgInternal params.Internal
ctx context.Context
ghcli common.GithubClient
id string
store dbCommon.Store
mux sync.Mutex
}
func (r *repository) PoolBalancerType() params.PoolBalancerType {
if r.cfgInternal.PoolBalancerType == "" {
return params.PoolBalancerTypeRoundRobin
}
return r.cfgInternal.PoolBalancerType
}
// nolint:golint,revive
// pool is used in enterprise and organzation
func (r *repository) GetJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (jitConfigMap map[string]string, runner *github.Runner, err error) {
req := github.GenerateJITConfigRequest{
Name: instance,
// At the repository level we only have the default runner group.
RunnerGroupID: 1,
Labels: labels,
// nolint:golangci-lint,godox
// TODO(gabriel-samfira): Should we make this configurable?
WorkFolder: github.String("_work"),
}
metrics.GithubOperationCount.WithLabelValues(
"GenerateRepoJITConfig", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
jitConfig, resp, err := r.ghcli.GenerateRepoJITConfig(ctx, r.cfg.Owner, r.cfg.Name, &req)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GenerateRepoJITConfig", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
if resp != nil && resp.StatusCode == http.StatusUnauthorized {
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
runner = jitConfig.Runner
defer func() {
if err != nil && runner != nil {
metrics.GithubOperationCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
_, innerErr := r.ghcli.RemoveRunner(r.ctx, r.cfg.Owner, r.cfg.Name, runner.GetID())
if innerErr != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
}
slog.With(slog.Any("error", innerErr)).ErrorContext(
ctx, "failed to remove runner",
"runner_id", runner.GetID(),
"repo", r.cfg.Name,
"owner", r.cfg.Owner)
}
}()
decoded, err := base64.StdEncoding.DecodeString(jitConfig.GetEncodedJITConfig())
if err != nil {
return nil, nil, fmt.Errorf("failed to decode JIT config: %w", err)
}
var ret map[string]string
if err := json.Unmarshal(decoded, &ret); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal JIT config: %w", err)
}
return ret, runner, nil
}
func (r *repository) GetRunnerInfoFromWorkflow(job params.WorkflowJob) (params.RunnerInfo, error) {
if err := r.ValidateOwner(job); err != nil {
return params.RunnerInfo{}, errors.Wrap(err, "validating owner")
}
metrics.GithubOperationCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
workflow, ghResp, err := r.ghcli.GetWorkflowJobByID(r.ctx, job.Repository.Owner.Login, job.Repository.Name, job.WorkflowJob.ID)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GetWorkflowJobByID", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return params.RunnerInfo{}, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching workflow info")
}
return params.RunnerInfo{}, errors.Wrap(err, "fetching workflow info")
}
if workflow.RunnerName != nil {
return params.RunnerInfo{
Name: *workflow.RunnerName,
Labels: workflow.Labels,
}, nil
}
return params.RunnerInfo{}, fmt.Errorf("failed to find runner name from workflow")
}
func (r *repository) UpdateState(param params.UpdatePoolStateParams) error {
r.mux.Lock()
defer r.mux.Unlock()
r.cfg.WebhookSecret = param.WebhookSecret
if param.InternalConfig != nil {
r.cfgInternal = *param.InternalConfig
}
ghc, _, err := util.GithubClient(r.ctx, r.cfgInternal.GithubCredentialsDetails)
if err != nil {
return errors.Wrap(err, "getting github client")
}
r.ghcli = ghc
return nil
}
func (r *repository) GetGithubRunners() ([]*github.Runner, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allRunners []*github.Runner
for {
metrics.GithubOperationCount.WithLabelValues(
"ListRunners", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
runners, ghResp, err := r.ghcli.ListRunners(r.ctx, r.cfg.Owner, r.cfg.Name, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListRunners", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (r *repository) FetchTools() ([]commonParams.RunnerApplicationDownload, error) {
r.mux.Lock()
defer r.mux.Unlock()
metrics.GithubOperationCount.WithLabelValues(
"ListRunnerApplicationDownloads", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
tools, ghResp, err := r.ghcli.ListRunnerApplicationDownloads(r.ctx, r.cfg.Owner, r.cfg.Name)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListRunnerApplicationDownloads", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching tools")
}
return nil, errors.Wrap(err, "fetching runner tools")
}
ret := []commonParams.RunnerApplicationDownload{}
for _, tool := range tools {
if tool == nil {
continue
}
ret = append(ret, commonParams.RunnerApplicationDownload(*tool))
}
return ret, nil
ctx context.Context
id string
store dbCommon.Store
}
func (r *repository) FetchDbInstances() ([]params.Instance, error) {
return r.store.ListRepoInstances(r.ctx, r.id)
}
func (r *repository) RemoveGithubRunner(runnerID int64) (*github.Response, error) {
metrics.GithubOperationCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
ghResp, err := r.ghcli.RemoveRunner(r.ctx, r.cfg.Owner, r.cfg.Name, runnerID)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"RemoveRunner", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
return nil, err
}
return ghResp, nil
}
func (r *repository) ListPools() ([]params.Pool, error) {
pools, err := r.store.ListRepoPools(r.ctx, r.id)
if err != nil {
@ -303,41 +98,6 @@ func (r *repository) ListPools() ([]params.Pool, error) {
return pools, nil
}
func (r *repository) GithubURL() string {
return fmt.Sprintf("%s/%s/%s", r.cfgInternal.GithubCredentialsDetails.BaseURL, r.cfg.Owner, r.cfg.Name)
}
func (r *repository) JwtToken() string {
return r.cfgInternal.JWTSecret
}
func (r *repository) GetGithubRegistrationToken() (string, error) {
metrics.GithubOperationCount.WithLabelValues(
"CreateRegistrationToken", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
tk, ghResp, err := r.ghcli.CreateRegistrationToken(r.ctx, r.cfg.Owner, r.cfg.Name)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"CreateRegistrationToken", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching token")
}
return "", errors.Wrap(err, "creating runner token")
}
return *tk.Token, nil
}
func (r *repository) String() string {
return fmt.Sprintf("%s/%s", r.cfg.Owner, r.cfg.Name)
}
func (r *repository) WebhookSecret() string {
return r.cfg.WebhookSecret
}
func (r *repository) GetPoolByID(poolID string) (params.Pool, error) {
pool, err := r.store.GetRepositoryPool(r.ctx, r.id, poolID)
if err != nil {
@ -345,129 +105,3 @@ func (r *repository) GetPoolByID(poolID string) (params.Pool, error) {
}
return pool, nil
}
func (r *repository) ValidateOwner(job params.WorkflowJob) error {
if !strings.EqualFold(job.Repository.Name, r.cfg.Name) || !strings.EqualFold(job.Repository.Owner.Login, r.cfg.Owner) {
return runnerErrors.NewBadRequestError("job not meant for this pool manager")
}
return nil
}
func (r *repository) ID() string {
return r.id
}
func (r *repository) listHooks(ctx context.Context) ([]*github.Hook, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allHooks []*github.Hook
for {
metrics.GithubOperationCount.WithLabelValues(
"ListRepoHooks", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
hooks, ghResp, err := r.ghcli.ListRepoHooks(ctx, r.cfg.Owner, r.cfg.Name, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListRepoHooks", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusNotFound {
return nil, runnerErrors.NewBadRequestError("repository not found or your PAT does not have access to manage webhooks")
}
return nil, errors.Wrap(err, "fetching hooks")
}
allHooks = append(allHooks, hooks...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allHooks, nil
}
func (r *repository) InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) {
allHooks, err := r.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
}
if err := validateHookRequest(r.cfgInternal.ControllerID, r.cfgInternal.BaseWebhookURL, allHooks, req); err != nil {
return params.HookInfo{}, errors.Wrap(err, "validating hook request")
}
metrics.GithubOperationCount.WithLabelValues(
"CreateRepoHook", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
hook, _, err := r.ghcli.CreateRepoHook(ctx, r.cfg.Owner, r.cfg.Name, req)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"CreateRepoHook", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
return params.HookInfo{}, errors.Wrap(err, "creating repository hook")
}
metrics.GithubOperationCount.WithLabelValues(
"PingRepoHook", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
if _, err := r.ghcli.PingRepoHook(ctx, r.cfg.Owner, r.cfg.Name, hook.GetID()); err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"PingRepoHook", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to ping hook",
"hook_id", hook.GetID(),
"repo", r.cfg.Name,
"owner", r.cfg.Owner)
}
return hookToParamsHookInfo(hook), nil
}
func (r *repository) UninstallHook(ctx context.Context, url string) error {
allHooks, err := r.listHooks(ctx)
if err != nil {
return errors.Wrap(err, "listing hooks")
}
for _, hook := range allHooks {
if hook.Config["url"] == url {
metrics.GithubOperationCount.WithLabelValues(
"DeleteRepoHook", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
_, err = r.ghcli.DeleteRepoHook(ctx, r.cfg.Owner, r.cfg.Name, hook.GetID())
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"DeleteRepoHook", // label: operation
params.MetricsLabelRepositoryScope, // label: scope
).Inc()
return errors.Wrap(err, "deleting hook")
}
return nil
}
}
return nil
}
func (r *repository) GetHookInfo(ctx context.Context) (params.HookInfo, error) {
allHooks, err := r.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
}
for _, hook := range allHooks {
hookInfo := hookToParamsHookInfo(hook)
if strings.EqualFold(hookInfo.URL, r.cfgInternal.ControllerWebhookURL) {
return hookInfo, nil
}
}
return params.HookInfo{}, runnerErrors.NewNotFoundError("hook not found")
}

View file

@ -16,10 +16,17 @@ package util
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"net/http"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/google/go-github/v57/github"
"github.com/pkg/errors"
"github.com/cloudbase/garm/metrics"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
)
@ -29,79 +36,410 @@ type githubClient struct {
org *github.OrganizationsService
repo *github.RepositoriesService
enterprise *github.EnterpriseService
entity params.GithubEntity
}
const (
metricsLabelEnterpriseScope = "Enterprise"
metricsLabelRepositoryScope = "Repository"
metricsLabelOrganizationScope = "Organization"
)
func (g *githubClient) ListOrgHooks(ctx context.Context, org string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
return g.org.ListHooks(ctx, org, opts)
func (g *githubClient) ListEntityHooks(ctx context.Context, opts *github.ListOptions) (ret []*github.Hook, response *github.Response, err error) {
metrics.GithubOperationCount.WithLabelValues(
"ListHooks", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListHooks", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, response, err = g.repo.ListHooks(ctx, g.entity.Owner, g.entity.Name, opts)
case params.GithubEntityTypeOrganization:
ret, response, err = g.org.ListHooks(ctx, g.entity.Owner, opts)
default:
return nil, nil, errors.New("invalid entity type")
}
return ret, response, err
}
func (g *githubClient) GetOrgHook(ctx context.Context, org string, id int64) (*github.Hook, *github.Response, error) {
return g.org.GetHook(ctx, org, id)
func (g *githubClient) GetEntityHook(ctx context.Context, id int64) (ret *github.Hook, err error) {
metrics.GithubOperationCount.WithLabelValues(
"GetHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GetHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, _, err = g.repo.GetHook(ctx, g.entity.Owner, g.entity.Name, id)
case params.GithubEntityTypeOrganization:
ret, _, err = g.org.GetHook(ctx, g.entity.Owner, id)
default:
return nil, errors.New("invalid entity type")
}
return ret, err
}
func (g *githubClient) CreateOrgHook(ctx context.Context, org string, hook *github.Hook) (*github.Hook, *github.Response, error) {
return g.org.CreateHook(ctx, org, hook)
func (g *githubClient) CreateEntityHook(ctx context.Context, hook *github.Hook) (ret *github.Hook, err error) {
metrics.GithubOperationCount.WithLabelValues(
"CreateHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"CreateHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, _, err = g.repo.CreateHook(ctx, g.entity.Owner, g.entity.Name, hook)
case params.GithubEntityTypeOrganization:
ret, _, err = g.org.CreateHook(ctx, g.entity.Owner, hook)
default:
return nil, errors.New("invalid entity type")
}
return ret, err
}
func (g *githubClient) DeleteOrgHook(ctx context.Context, org string, id int64) (*github.Response, error) {
return g.org.DeleteHook(ctx, org, id)
func (g *githubClient) DeleteEntityHook(ctx context.Context, id int64) (ret *github.Response, err error) {
metrics.GithubOperationCount.WithLabelValues(
"DeleteHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"DeleteHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, err = g.repo.DeleteHook(ctx, g.entity.Owner, g.entity.Name, id)
case params.GithubEntityTypeOrganization:
ret, err = g.org.DeleteHook(ctx, g.entity.Owner, id)
default:
return nil, errors.New("invalid entity type")
}
return ret, err
}
func (g *githubClient) PingOrgHook(ctx context.Context, org string, id int64) (*github.Response, error) {
return g.org.PingHook(ctx, org, id)
func (g *githubClient) PingEntityHook(ctx context.Context, id int64) (ret *github.Response, err error) {
metrics.GithubOperationCount.WithLabelValues(
"PingHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"PingHook", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, err = g.repo.PingHook(ctx, g.entity.Owner, g.entity.Name, id)
case params.GithubEntityTypeOrganization:
ret, err = g.org.PingHook(ctx, g.entity.Owner, id)
default:
return nil, errors.New("invalid entity type")
}
return ret, err
}
func (g *githubClient) ListRepoHooks(ctx context.Context, owner, repo string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
return g.repo.ListHooks(ctx, owner, repo, opts)
}
func (g *githubClient) GetRepoHook(ctx context.Context, owner, repo string, id int64) (*github.Hook, *github.Response, error) {
return g.repo.GetHook(ctx, owner, repo, id)
}
func (g *githubClient) CreateRepoHook(ctx context.Context, owner, repo string, hook *github.Hook) (*github.Hook, *github.Response, error) {
return g.repo.CreateHook(ctx, owner, repo, hook)
}
func (g *githubClient) DeleteRepoHook(ctx context.Context, owner, repo string, id int64) (*github.Response, error) {
return g.repo.DeleteHook(ctx, owner, repo, id)
}
func (g *githubClient) PingRepoHook(ctx context.Context, owner, repo string, id int64) (*github.Response, error) {
return g.repo.PingHook(ctx, owner, repo, id)
}
func (g *githubClient) ListEntityRunners(ctx context.Context, entity params.GithubEntity, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
var runners *github.Runners
func (g *githubClient) ListEntityRunners(ctx context.Context, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
var ret *github.Runners
var response *github.Response
var err error
switch entity.EntityType {
metrics.GithubOperationCount.WithLabelValues(
"ListEntityRunners", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListEntityRunners", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
runners, response, err = g.ListRunners(ctx, entity.Owner, entity.Name, opts)
ret, response, err = g.ListRunners(ctx, g.entity.Owner, g.entity.Name, opts)
case params.GithubEntityTypeOrganization:
runners, response, err = g.ListOrganizationRunners(ctx, entity.Owner, opts)
ret, response, err = g.ListOrganizationRunners(ctx, g.entity.Owner, opts)
case params.GithubEntityTypeEnterprise:
runners, response, err = g.enterprise.ListRunners(ctx, entity.Owner, opts)
ret, response, err = g.enterprise.ListRunners(ctx, g.entity.Owner, opts)
default:
return nil, nil, errors.New("invalid entity type")
}
return runners, response, err
return ret, response, err
}
func GithubClient(_ context.Context, credsDetails params.GithubCredentials) (common.GithubClient, common.GithubEnterpriseClient, error) {
func (g *githubClient) ListEntityRunnerApplicationDownloads(ctx context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error) {
var ret []*github.RunnerApplicationDownload
var response *github.Response
var err error
metrics.GithubOperationCount.WithLabelValues(
"ListEntityRunnerApplicationDownloads", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListEntityRunnerApplicationDownloads", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, response, err = g.ListRunnerApplicationDownloads(ctx, g.entity.Owner, g.entity.Name)
case params.GithubEntityTypeOrganization:
ret, response, err = g.ListOrganizationRunnerApplicationDownloads(ctx, g.entity.Owner)
case params.GithubEntityTypeEnterprise:
ret, response, err = g.enterprise.ListRunnerApplicationDownloads(ctx, g.entity.Owner)
default:
return nil, nil, errors.New("invalid entity type")
}
return ret, response, err
}
func (g *githubClient) RemoveEntityRunner(ctx context.Context, runnerID int64) (*github.Response, error) {
var response *github.Response
var err error
metrics.GithubOperationCount.WithLabelValues(
"RemoveEntityRunner", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"RemoveEntityRunner", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
response, err = g.RemoveRunner(ctx, g.entity.Owner, g.entity.Name, runnerID)
case params.GithubEntityTypeOrganization:
response, err = g.RemoveOrganizationRunner(ctx, g.entity.Owner, runnerID)
case params.GithubEntityTypeEnterprise:
response, err = g.enterprise.RemoveRunner(ctx, g.entity.Owner, runnerID)
default:
return nil, errors.New("invalid entity type")
}
return response, err
}
func (g *githubClient) CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error) {
var ret *github.RegistrationToken
var response *github.Response
var err error
metrics.GithubOperationCount.WithLabelValues(
"CreateEntityRegistrationToken", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
defer func() {
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"CreateEntityRegistrationToken", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
}
}()
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, response, err = g.CreateRegistrationToken(ctx, g.entity.Owner, g.entity.Name)
case params.GithubEntityTypeOrganization:
ret, response, err = g.CreateOrganizationRegistrationToken(ctx, g.entity.Owner)
case params.GithubEntityTypeEnterprise:
ret, response, err = g.enterprise.CreateRegistrationToken(ctx, g.entity.Owner)
default:
return nil, nil, errors.New("invalid entity type")
}
return ret, response, err
}
func (g *githubClient) getOrganizationRunnerGroupIDByName(ctx context.Context, entity params.GithubEntity, rgName string) (int64, error) {
opts := github.ListOrgRunnerGroupOptions{
ListOptions: github.ListOptions{
PerPage: 100,
},
}
for {
metrics.GithubOperationCount.WithLabelValues(
"ListOrganizationRunnerGroups", // label: operation
entity.LabelScope(), // label: scope
).Inc()
runnerGroups, ghResp, err := g.ListOrganizationRunnerGroups(ctx, entity.Owner, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListOrganizationRunnerGroups", // label: operation
entity.LabelScope(), // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return 0, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return 0, errors.Wrap(err, "fetching runners")
}
for _, runnerGroup := range runnerGroups.RunnerGroups {
if runnerGroup.Name != nil && *runnerGroup.Name == rgName {
return *runnerGroup.ID, nil
}
}
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return 0, runnerErrors.NewNotFoundError("runner group not found")
}
func (g *githubClient) getEnterpriseRunnerGroupIDByName(ctx context.Context, entity params.GithubEntity, rgName string) (int64, error) {
opts := github.ListEnterpriseRunnerGroupOptions{
ListOptions: github.ListOptions{
PerPage: 100,
},
}
for {
metrics.GithubOperationCount.WithLabelValues(
"ListRunnerGroups", // label: operation
entity.LabelScope(), // label: scope
).Inc()
runnerGroups, ghResp, err := g.enterprise.ListRunnerGroups(ctx, entity.Owner, &opts)
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"ListRunnerGroups", // label: operation
entity.LabelScope(), // label: scope
).Inc()
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return 0, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return 0, errors.Wrap(err, "fetching runners")
}
for _, runnerGroup := range runnerGroups.RunnerGroups {
if runnerGroup.Name != nil && *runnerGroup.Name == rgName {
return *runnerGroup.ID, nil
}
}
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return 0, runnerErrors.NewNotFoundError("runner group not found")
}
func (g *githubClient) GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (jitConfigMap map[string]string, runner *github.Runner, err error) {
var rgID int64
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
rgID = 1
case params.GithubEntityTypeOrganization:
rgID, err = g.getOrganizationRunnerGroupIDByName(ctx, g.entity, pool.GitHubRunnerGroup)
case params.GithubEntityTypeEnterprise:
rgID, err = g.getEnterpriseRunnerGroupIDByName(ctx, g.entity, pool.GitHubRunnerGroup)
}
if err != nil {
return nil, nil, fmt.Errorf("getting runner group ID: %w", err)
}
req := github.GenerateJITConfigRequest{
Name: instance,
RunnerGroupID: rgID,
Labels: labels,
// nolint:golangci-lint,godox
// TODO(gabriel-samfira): Should we make this configurable?
WorkFolder: github.String("_work"),
}
metrics.GithubOperationCount.WithLabelValues(
"GetEntityJITConfig", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
var ret *github.JITRunnerConfig
var response *github.Response
switch g.entity.EntityType {
case params.GithubEntityTypeRepository:
ret, response, err = g.GenerateRepoJITConfig(ctx, g.entity.Owner, g.entity.Name, &req)
case params.GithubEntityTypeOrganization:
ret, response, err = g.GenerateOrgJITConfig(ctx, g.entity.Owner, &req)
case params.GithubEntityTypeEnterprise:
ret, response, err = g.enterprise.GenerateEnterpriseJITConfig(ctx, g.entity.Owner, &req)
}
if err != nil {
metrics.GithubOperationFailedCount.WithLabelValues(
"GetEntityJITConfig", // label: operation
g.entity.LabelScope(), // label: scope
).Inc()
if response != nil && response.StatusCode == http.StatusUnauthorized {
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
return nil, nil, fmt.Errorf("failed to get JIT config: %w", err)
}
defer func(run *github.Runner) {
if err != nil && run != nil {
_, innerErr := g.RemoveEntityRunner(ctx, run.GetID())
slog.With(slog.Any("error", innerErr)).ErrorContext(
ctx, "failed to remove runner",
"runner_id", run.GetID(), string(g.entity.EntityType), g.entity.String())
}
}(ret.Runner)
decoded, err := base64.StdEncoding.DecodeString(*ret.EncodedJITConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to decode JIT config: %w", err)
}
var jitConfig map[string]string
if err := json.Unmarshal(decoded, &jitConfig); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal JIT config: %w", err)
}
return jitConfig, ret.Runner, nil
}
func GithubClient(_ context.Context, entity params.GithubEntity, credsDetails params.GithubCredentials) (common.GithubClient, error) {
if credsDetails.HTTPClient == nil {
return nil, nil, errors.New("http client is nil")
return nil, errors.New("http client is nil")
}
ghClient, err := github.NewClient(credsDetails.HTTPClient).WithEnterpriseURLs(credsDetails.APIBaseURL, credsDetails.UploadBaseURL)
if err != nil {
return nil, nil, errors.Wrap(err, "fetching github client")
return nil, errors.Wrap(err, "fetching github client")
}
cli := &githubClient{
@ -109,6 +447,7 @@ func GithubClient(_ context.Context, credsDetails params.GithubCredentials) (com
org: ghClient.Organizations,
repo: ghClient.Repositories,
enterprise: ghClient.Enterprise,
entity: entity,
}
return cli, ghClient.Enterprise, nil
return cli, nil
}