Merge pull request #408 from gabriel-samfira/fix-add-entity-event

Fix AddInstanceEvent and expose events
This commit is contained in:
Gabriel 2025-05-22 22:24:36 +03:00 committed by GitHub
commit 451e7c4556
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 273 additions and 60 deletions

View file

@ -16,6 +16,7 @@ package cmd
import (
"fmt"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@ -250,9 +251,15 @@ func formatOneEnterprise(enterprise params.Enterprise) {
t.AppendRow(table.Row{"Pools", pool.ID}, rowConfigAutoMerge)
}
}
if len(enterprise.Events) > 0 {
for _, event := range enterprise.Events {
t.AppendRow(table.Row{"Events", fmt.Sprintf("%s %s: %s", event.CreatedAt.Format("2006-01-02T15:04:05"), strings.ToUpper(string(event.EventLevel)), event.Message)}, rowConfigAutoMerge)
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())

View file

@ -16,6 +16,7 @@ package cmd
import (
"fmt"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@ -394,9 +395,14 @@ func formatOneOrganization(org params.Organization) {
t.AppendRow(table.Row{"Pools", pool.ID}, rowConfigAutoMerge)
}
}
if len(org.Events) > 0 {
for _, event := range org.Events {
t.AppendRow(table.Row{"Events", fmt.Sprintf("%s %s: %s", event.CreatedAt.Format("2006-01-02T15:04:05"), strings.ToUpper(string(event.EventLevel)), event.Message)}, rowConfigAutoMerge)
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())

View file

@ -476,7 +476,7 @@ func formatPools(pools []params.Pool) {
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 2, WidthMax: 40},
})
header := table.Row{"ID", "Image", "Flavor", "Tags", "Belongs to", "Enabled"}
header := table.Row{"ID", "Image", "Flavor", "Tags", "Belongs to", "Endpoint", "Forge Type", "Enabled"}
if long {
header = append(header, "Level", "Created At", "Updated at", "Runner Prefix", "Priority")
}
@ -501,7 +501,7 @@ func formatPools(pools []params.Pool) {
belongsTo = pool.EnterpriseName
level = entityTypeEnterprise
}
row := table.Row{pool.ID, pool.Image, pool.Flavor, strings.Join(tags, " "), belongsTo, pool.Enabled}
row := table.Row{pool.ID, pool.Image, pool.Flavor, strings.Join(tags, " "), belongsTo, pool.Endpoint.Name, pool.Endpoint.EndpointType, pool.Enabled}
if long {
row = append(row, level, pool.CreatedAt, pool.UpdatedAt, pool.GetRunnerPrefix(), pool.Priority)
}
@ -561,6 +561,8 @@ func formatOnePool(pool params.Pool) {
t.AppendRow(table.Row{"Runner Prefix", pool.GetRunnerPrefix()})
t.AppendRow(table.Row{"Extra specs", string(pool.ExtraSpecs)})
t.AppendRow(table.Row{"GitHub Runner Group", pool.GitHubRunnerGroup})
t.AppendRow(table.Row{"Forge Type", pool.Endpoint.EndpointType})
t.AppendRow(table.Row{"Endpoint Name", pool.Endpoint.Name})
if len(pool.Instances) > 0 {
for _, instance := range pool.Instances {

View file

@ -16,6 +16,7 @@ package cmd
import (
"fmt"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@ -404,9 +405,16 @@ func formatOneRepository(repo params.Repository) {
t.AppendRow(table.Row{"Pools", pool.ID}, rowConfigAutoMerge)
}
}
if len(repo.Events) > 0 {
for _, event := range repo.Events {
t.AppendRow(table.Row{"Events", fmt.Sprintf("%s %s: %s", event.CreatedAt.Format("2006-01-02T15:04:05"), strings.ToUpper(string(event.EventLevel)), event.Message)}, rowConfigAutoMerge)
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())

View file

@ -97,7 +97,7 @@ func (_m *Store) ControllerInfo() (params.ControllerInfo, error) {
}
// CreateEnterprise provides a mock function with given fields: ctx, name, credentialsName, webhookSecret, poolBalancerType
func (_m *Store) CreateEnterprise(ctx context.Context, name string, credentialsName string, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Enterprise, error) {
func (_m *Store) CreateEnterprise(ctx context.Context, name string, credentialsName params.ForgeCredentials, webhookSecret string, poolBalancerType params.PoolBalancerType) (params.Enterprise, error) {
ret := _m.Called(ctx, name, credentialsName, webhookSecret, poolBalancerType)
if len(ret) == 0 {
@ -106,16 +106,16 @@ func (_m *Store) CreateEnterprise(ctx context.Context, name string, credentialsN
var r0 params.Enterprise
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, params.PoolBalancerType) (params.Enterprise, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, string, params.ForgeCredentials, string, params.PoolBalancerType) (params.Enterprise, error)); ok {
return rf(ctx, name, credentialsName, webhookSecret, poolBalancerType)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, params.PoolBalancerType) params.Enterprise); ok {
if rf, ok := ret.Get(0).(func(context.Context, string, params.ForgeCredentials, string, params.PoolBalancerType) params.Enterprise); ok {
r0 = rf(ctx, name, credentialsName, webhookSecret, poolBalancerType)
} else {
r0 = ret.Get(0).(params.Enterprise)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, params.PoolBalancerType) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, string, params.ForgeCredentials, string, params.PoolBalancerType) error); ok {
r1 = rf(ctx, name, credentialsName, webhookSecret, poolBalancerType)
} else {
r1 = ret.Error(1)

View file

@ -70,12 +70,12 @@ func (s *sqlDatabase) CreateEnterprise(ctx context.Context, name string, credent
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
}
paramEnt, err = s.sqlToCommonEnterprise(newEnterprise, true)
ret, err := s.GetEnterpriseByID(ctx, newEnterprise.ID.String())
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
}
return paramEnt, nil
return ret, nil
}
func (s *sqlDatabase) GetEnterprise(ctx context.Context, name, endpointName string) (params.Enterprise, error) {
@ -92,7 +92,14 @@ func (s *sqlDatabase) GetEnterprise(ctx context.Context, name, endpointName stri
}
func (s *sqlDatabase) GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error) {
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID, "Pools", "Credentials", "Endpoint", "Credentials.Endpoint")
preloadList := []string{
"Pools",
"Credentials",
"Endpoint",
"Credentials.Endpoint",
"Events",
}
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID, preloadList...)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}

View file

@ -441,6 +441,10 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseByIDDBDecryptingErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `enterprises` WHERE id = ? AND `enterprises`.`deleted_at` IS NULL ORDER BY `enterprises`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Enterprises[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Enterprises[0].ID))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `enterprise_events` WHERE `enterprise_events`.`enterprise_id` = ? AND `enterprise_events`.`deleted_at` IS NULL")).
WithArgs(s.Fixtures.Enterprises[0].ID).
WillReturnRows(sqlmock.NewRows([]string{"enterprise_id"}).AddRow(s.Fixtures.Enterprises[0].ID))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `pools` WHERE `pools`.`enterprise_id` = ? AND `pools`.`deleted_at` IS NULL")).
WithArgs(s.Fixtures.Enterprises[0].ID).
@ -773,6 +777,28 @@ func (s *EnterpriseTestSuite) TestUpdateEnterprisePoolInvalidEnterpriseID() {
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestAddRepoEntityEvent() {
enterprise, err := s.Store.CreateEnterprise(
s.adminCtx,
s.Fixtures.CreateEnterpriseParams.Name,
s.testCreds,
s.Fixtures.CreateEnterpriseParams.WebhookSecret,
params.PoolBalancerTypeRoundRobin)
s.Require().Nil(err)
entity, err := enterprise.GetEntity()
s.Require().Nil(err)
err = s.Store.AddEntityEvent(s.adminCtx, entity, params.StatusEvent, params.EventInfo, "this is a test", 20)
s.Require().Nil(err)
enterprise, err = s.Store.GetEnterpriseByID(s.adminCtx, enterprise.ID)
s.Require().Nil(err)
s.Require().Equal(1, len(enterprise.Events))
s.Require().Equal(params.StatusEvent, enterprise.Events[0].EventType)
s.Require().Equal(params.EventInfo, enterprise.Events[0].EventLevel)
s.Require().Equal("this is a test", enterprise.Events[0].Message)
}
func TestEnterpriseTestSuite(t *testing.T) {
suite.Run(t, new(EnterpriseTestSuite))
}

View file

@ -185,7 +185,7 @@ type Repository struct {
EndpointName *string `gorm:"index:idx_owner_nocase,unique,collate:nocase"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
Events []*RepositoryEvent `gorm:"foreignKey:RepoID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Events []RepositoryEvent `gorm:"foreignKey:RepoID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
}
type OrganizationEvent struct {
@ -217,7 +217,7 @@ type Organization struct {
EndpointName *string `gorm:"index:idx_org_name_nocase,collate:nocase"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
Events []*OrganizationEvent `gorm:"foreignKey:OrgID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Events []OrganizationEvent `gorm:"foreignKey:OrgID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
}
type EnterpriseEvent struct {
@ -247,7 +247,7 @@ type Enterprise struct {
EndpointName *string `gorm:"index:idx_ent_name_nocase,collate:nocase"`
Endpoint GithubEndpoint `gorm:"foreignKey:EndpointName;constraint:OnDelete:SET NULL"`
Events []*EnterpriseEvent `gorm:"foreignKey:EnterpriseID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Events []EnterpriseEvent `gorm:"foreignKey:EnterpriseID;constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
}
type Address struct {

View file

@ -70,17 +70,12 @@ func (s *sqlDatabase) CreateOrganization(ctx context.Context, name string, crede
return params.Organization{}, errors.Wrap(err, "creating org")
}
org, err := s.getOrgByID(ctx, s.conn, newOrg.ID.String(), "Pools", "Endpoint", "Credentials", "GiteaCredentials", "Credentials.Endpoint", "GiteaCredentials.Endpoint")
ret, err := s.GetOrganizationByID(ctx, newOrg.ID.String())
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org")
}
param, err = s.sqlToCommonOrganization(org, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org")
}
return param, nil
return ret, nil
}
func (s *sqlDatabase) GetOrganization(ctx context.Context, name, endpointName string) (params.Organization, error) {
@ -215,7 +210,16 @@ func (s *sqlDatabase) UpdateOrganization(ctx context.Context, orgID string, para
}
func (s *sqlDatabase) GetOrganizationByID(ctx context.Context, orgID string) (params.Organization, error) {
org, err := s.getOrgByID(ctx, s.conn, orgID, "Pools", "Credentials", "Endpoint", "Credentials.Endpoint", "GiteaCredentials", "GiteaCredentials.Endpoint")
preloadList := []string{
"Pools",
"Credentials",
"Endpoint",
"Credentials.Endpoint",
"GiteaCredentials",
"GiteaCredentials.Endpoint",
"Events",
}
org, err := s.getOrgByID(ctx, s.conn, orgID, preloadList...)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
}

View file

@ -502,6 +502,10 @@ func (s *OrgTestSuite) TestGetOrganizationByIDDBDecryptingErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `organizations` WHERE id = ? AND `organizations`.`deleted_at` IS NULL ORDER BY `organizations`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Orgs[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Orgs[0].ID))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `organization_events` WHERE `organization_events`.`org_id` = ? AND `organization_events`.`deleted_at` IS NULL")).
WithArgs(s.Fixtures.Orgs[0].ID).
WillReturnRows(sqlmock.NewRows([]string{"org_id"}).AddRow(s.Fixtures.Orgs[0].ID))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `pools` WHERE `pools`.`org_id` = ? AND `pools`.`deleted_at` IS NULL")).
WithArgs(s.Fixtures.Orgs[0].ID).
@ -826,6 +830,28 @@ func (s *OrgTestSuite) TestUpdateOrganizationPool() {
s.Require().Equal(s.Fixtures.UpdatePoolParams.Flavor, pool.Flavor)
}
func (s *OrgTestSuite) TestAddOrgEntityEvent() {
org, err := s.Store.CreateOrganization(
s.adminCtx,
s.Fixtures.CreateOrgParams.Name,
s.testCreds,
s.Fixtures.CreateOrgParams.WebhookSecret,
params.PoolBalancerTypeRoundRobin)
s.Require().Nil(err)
entity, err := org.GetEntity()
s.Require().Nil(err)
err = s.Store.AddEntityEvent(s.adminCtx, entity, params.StatusEvent, params.EventInfo, "this is a test", 20)
s.Require().Nil(err)
org, err = s.Store.GetOrganizationByID(s.adminCtx, org.ID)
s.Require().Nil(err)
s.Require().Equal(1, len(org.Events))
s.Require().Equal(params.StatusEvent, org.Events[0].EventType)
s.Require().Equal(params.EventInfo, org.Events[0].EventLevel)
s.Require().Equal("this is a test", org.Events[0].Message)
}
func (s *OrgTestSuite) TestUpdateOrganizationPoolInvalidOrgID() {
entity := params.ForgeEntity{
ID: "dummy-org-id",

View file

@ -40,8 +40,11 @@ func (s *sqlDatabase) ListAllPools(_ context.Context) ([]params.Pool, error) {
q := s.conn.Model(&Pool{}).
Preload("Tags").
Preload("Organization").
Preload("Organization.Endpoint").
Preload("Repository").
Preload("Repository.Endpoint").
Preload("Enterprise").
Preload("Enterprise.Endpoint").
Omit("extra_specs").
Find(&pools)
if q.Error != nil {
@ -60,7 +63,17 @@ func (s *sqlDatabase) ListAllPools(_ context.Context) ([]params.Pool, error) {
}
func (s *sqlDatabase) GetPoolByID(_ context.Context, poolID string) (params.Pool, error) {
pool, err := s.getPoolByID(s.conn, poolID, "Tags", "Instances", "Enterprise", "Organization", "Repository")
preloadList := []string{
"Tags",
"Instances",
"Enterprise",
"Enterprise.Endpoint",
"Organization",
"Organization.Endpoint",
"Repository",
"Repository.Endpoint",
}
pool, err := s.getPoolByID(s.conn, poolID, preloadList...)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool by ID")
}

View file

@ -71,17 +71,12 @@ func (s *sqlDatabase) CreateRepository(ctx context.Context, owner, name string,
return params.Repository{}, errors.Wrap(err, "creating repository")
}
repo, err := s.getRepoByID(ctx, s.conn, newRepo.ID.String(), "Endpoint", "Credentials", "GiteaCredentials", "Credentials.Endpoint", "GiteaCredentials.Endpoint")
ret, err := s.GetRepositoryByID(ctx, newRepo.ID.String())
if err != nil {
return params.Repository{}, errors.Wrap(err, "creating repository")
}
param, err = s.sqlToCommonRepository(repo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "creating repository")
}
return param, nil
return ret, nil
}
func (s *sqlDatabase) GetRepository(ctx context.Context, owner, name, endpointName string) (params.Repository, error) {
@ -217,7 +212,16 @@ func (s *sqlDatabase) UpdateRepository(ctx context.Context, repoID string, param
}
func (s *sqlDatabase) GetRepositoryByID(ctx context.Context, repoID string) (params.Repository, error) {
repo, err := s.getRepoByID(ctx, s.conn, repoID, "Pools", "Credentials", "Endpoint", "Credentials.Endpoint", "GiteaCredentials", "GiteaCredentials.Endpoint")
preloadList := []string{
"Pools",
"Credentials",
"Endpoint",
"Credentials.Endpoint",
"GiteaCredentials",
"GiteaCredentials.Endpoint",
"Events",
}
repo, err := s.getRepoByID(ctx, s.conn, repoID, preloadList...)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
}

View file

@ -558,6 +558,10 @@ func (s *RepoTestSuite) TestGetRepositoryByIDDBDecryptingErr() {
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repositories` WHERE id = ? AND `repositories`.`deleted_at` IS NULL ORDER BY `repositories`.`id` LIMIT ?")).
WithArgs(s.Fixtures.Repos[0].ID, 1).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(s.Fixtures.Repos[0].ID))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `repository_events` WHERE `repository_events`.`repo_id` = ? AND `repository_events`.`deleted_at` IS NULL")).
WithArgs(s.Fixtures.Repos[0].ID).
WillReturnRows(sqlmock.NewRows([]string{"repo_id"}).AddRow(s.Fixtures.Repos[0].ID))
s.Fixtures.SQLMock.
ExpectQuery(regexp.QuoteMeta("SELECT * FROM `pools` WHERE `pools`.`repo_id` = ? AND `pools`.`deleted_at` IS NULL")).
WithArgs(s.Fixtures.Repos[0].ID).
@ -894,6 +898,29 @@ func (s *RepoTestSuite) TestUpdateRepositoryPoolInvalidRepoID() {
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestAddRepoEntityEvent() {
repo, err := s.Store.CreateRepository(
s.adminCtx,
s.Fixtures.CreateRepoParams.Owner,
s.Fixtures.CreateRepoParams.Name,
s.testCreds,
s.Fixtures.CreateRepoParams.WebhookSecret,
params.PoolBalancerTypeRoundRobin)
s.Require().Nil(err)
entity, err := repo.GetEntity()
s.Require().Nil(err)
err = s.Store.AddEntityEvent(s.adminCtx, entity, params.StatusEvent, params.EventInfo, "this is a test", 20)
s.Require().Nil(err)
repo, err = s.Store.GetRepositoryByID(s.adminCtx, repo.ID)
s.Require().Nil(err)
s.Require().Equal(1, len(repo.Events))
s.Require().Equal(params.StatusEvent, repo.Events[0].EventType)
s.Require().Equal(params.EventInfo, repo.Events[0].EventLevel)
s.Require().Equal("this is a test", repo.Events[0].Message)
}
func TestRepoTestSuite(t *testing.T) {
suite.Run(t, new(RepoTestSuite))
}

View file

@ -166,6 +166,19 @@ func (s *sqlDatabase) sqlToCommonOrganization(org Organization, detailed bool) (
return params.Organization{}, errors.Wrap(err, "converting credentials")
}
if len(org.Events) > 0 {
ret.Events = make([]params.EntityEvent, len(org.Events))
for idx, event := range org.Events {
ret.Events[idx] = params.EntityEvent{
ID: event.ID,
Message: event.Message,
EventType: event.EventType,
EventLevel: event.EventLevel,
CreatedAt: event.CreatedAt,
}
}
}
if detailed {
ret.Credentials = forgeCreds
ret.CredentialsName = forgeCreds.Name
@ -214,6 +227,19 @@ func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise, detailed bool
ret.CredentialsID = *enterprise.CredentialsID
}
if len(enterprise.Events) > 0 {
ret.Events = make([]params.EntityEvent, len(enterprise.Events))
for idx, event := range enterprise.Events {
ret.Events[idx] = params.EntityEvent{
ID: event.ID,
Message: event.Message,
EventType: event.EventType,
EventLevel: event.EventLevel,
CreatedAt: event.CreatedAt,
}
}
}
if detailed {
creds, err := s.sqlToCommonForgeCredentials(enterprise.Credentials)
if err != nil {
@ -260,28 +286,37 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) (params.Pool, error) {
UpdatedAt: pool.UpdatedAt,
}
var ep GithubEndpoint
if pool.RepoID != nil {
ret.RepoID = pool.RepoID.String()
if pool.Repository.Owner != "" && pool.Repository.Name != "" {
ret.RepoName = fmt.Sprintf("%s/%s", pool.Repository.Owner, pool.Repository.Name)
}
ep = pool.Repository.Endpoint
}
if pool.OrgID != nil && pool.Organization.Name != "" {
ret.OrgID = pool.OrgID.String()
ret.OrgName = pool.Organization.Name
ep = pool.Organization.Endpoint
}
if pool.EnterpriseID != nil && pool.Enterprise.Name != "" {
ret.EnterpriseID = pool.EnterpriseID.String()
ret.EnterpriseName = pool.Enterprise.Name
ep = pool.Enterprise.Endpoint
}
endpoint, err := s.sqlToCommonGithubEndpoint(ep)
if err != nil {
return params.Pool{}, errors.Wrap(err, "converting endpoint")
}
ret.Endpoint = endpoint
for idx, val := range pool.Tags {
ret.Tags[idx] = s.sqlToCommonTags(*val)
}
var err error
for idx, inst := range pool.Instances {
ret.Instances[idx], err = s.sqlToParamsInstance(inst)
if err != nil {
@ -399,6 +434,19 @@ func (s *sqlDatabase) sqlToCommonRepository(repo Repository, detailed bool) (par
return params.Repository{}, errors.Wrap(err, "converting credentials")
}
if len(repo.Events) > 0 {
ret.Events = make([]params.EntityEvent, len(repo.Events))
for idx, event := range repo.Events {
ret.Events[idx] = params.EntityEvent{
ID: event.ID,
Message: event.Message,
EventType: event.EventType,
EventLevel: event.EventLevel,
CreatedAt: event.CreatedAt,
}
}
}
if detailed {
ret.Credentials = forgeCreds
ret.CredentialsName = forgeCreds.Name
@ -654,7 +702,7 @@ func (s *sqlDatabase) GetForgeEntity(_ context.Context, entityType params.ForgeE
}
func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
repo, err := s.GetRepositoryByID(ctx, repoID)
repo, err := s.getRepoByID(ctx, s.conn, repoID)
if err != nil {
return errors.Wrap(err, "updating instance")
}
@ -670,20 +718,16 @@ func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, eve
}
if maxEvents > 0 {
repoID, err := uuid.Parse(repo.ID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var latestEvents []RepositoryEvent
q := s.conn.Model(&RepositoryEvent{}).
Limit(maxEvents).Order("id desc").
Where("repo_id = ?", repoID).Find(&latestEvents)
Where("repo_id = ?", repo.ID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("repo_id = ? and id < ?", repoID, lastInList.ID).Unscoped().Delete(&RepositoryEvent{}).Error; err != nil {
if err := s.conn.Where("repo_id = ? and id < ?", repo.ID, lastInList.ID).Unscoped().Delete(&RepositoryEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
}
}
@ -692,7 +736,7 @@ func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, eve
}
func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
org, err := s.GetOrganizationByID(ctx, orgID)
org, err := s.getOrgByID(ctx, s.conn, orgID)
if err != nil {
return errors.Wrap(err, "updating instance")
}
@ -708,20 +752,16 @@ func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event param
}
if maxEvents > 0 {
orgID, err := uuid.Parse(org.ID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var latestEvents []OrganizationEvent
q := s.conn.Model(&OrganizationEvent{}).
Limit(maxEvents).Order("id desc").
Where("org_id = ?", orgID).Find(&latestEvents)
Where("org_id = ?", org.ID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("org_id = ? and id < ?", orgID, lastInList.ID).Unscoped().Delete(&OrganizationEvent{}).Error; err != nil {
if err := s.conn.Where("org_id = ? and id < ?", org.ID, lastInList.ID).Unscoped().Delete(&OrganizationEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
}
}
@ -730,7 +770,7 @@ func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event param
}
func (s *sqlDatabase) addEnterpriseEvent(ctx context.Context, entID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
ent, err := s.GetEnterpriseByID(ctx, entID)
ent, err := s.getEnterpriseByID(ctx, s.conn, entID)
if err != nil {
return errors.Wrap(err, "updating instance")
}
@ -746,20 +786,16 @@ func (s *sqlDatabase) addEnterpriseEvent(ctx context.Context, entID string, even
}
if maxEvents > 0 {
entID, err := uuid.Parse(ent.ID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var latestEvents []EnterpriseEvent
q := s.conn.Model(&EnterpriseEvent{}).
Limit(maxEvents).Order("id desc").
Where("enterprise_id = ?", entID).Find(&latestEvents)
Where("enterprise_id = ?", ent.ID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("enterprise_id = ? and id < ?", entID, lastInList.ID).Unscoped().Delete(&EnterpriseEvent{}).Error; err != nil {
if err := s.conn.Where("enterprise_id = ? and id < ?", ent.ID, lastInList.ID).Unscoped().Delete(&EnterpriseEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
}
}

View file

@ -177,6 +177,15 @@ type StatusMessage struct {
EventLevel EventLevel `json:"event_level,omitempty"`
}
type EntityEvent struct {
ID uint `json:"id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
EventType EventType `json:"event_type,omitempty"`
EventLevel EventLevel `json:"event_level,omitempty"`
Message string `json:"message,omitempty"`
}
type Instance struct {
// ID is the database ID of this instance.
ID string `json:"id,omitempty"`
@ -365,6 +374,8 @@ type Pool struct {
EnterpriseID string `json:"enterprise_id,omitempty"`
EnterpriseName string `json:"enterprise_name,omitempty"`
Endpoint ForgeEndpoint `json:"forge_type,omitempty"`
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
@ -600,6 +611,7 @@ type Repository struct {
Endpoint ForgeEndpoint `json:"endpoint,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Events []EntityEvent `json:"events,omitempty"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
@ -669,6 +681,7 @@ type Organization struct {
Endpoint ForgeEndpoint `json:"endpoint,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Events []EntityEvent `json:"events,omitempty"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
@ -726,6 +739,7 @@ type Enterprise struct {
Endpoint ForgeEndpoint `json:"endpoint,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Events []EntityEvent `json:"events,omitempty"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}

View file

@ -245,7 +245,6 @@ func parseError(response *github.Response, err error) error {
statusCode = response.StatusCode
}
slog.Debug("parsing error", "status_code", statusCode, "response", response, "error", err)
switch statusCode {
case http.StatusNotFound:
return runnerErrors.ErrNotFound

View file

@ -130,7 +130,7 @@ func (w *Worker) loadAllEntities() error {
}
for _, entity := range cache.GetAllEntities() {
worker := newToolsUpdater(w.ctx, entity)
worker := newToolsUpdater(w.ctx, entity, w.store)
if err := worker.Start(); err != nil {
return fmt.Errorf("starting tools updater: %w", err)
}
@ -286,7 +286,7 @@ func (w *Worker) handleEntityEvent(entityGetter params.EntityGetter, op common.O
cache.SetEntity(entity)
worker, ok := w.toolsWorkes[entity.ID]
if !ok {
worker = newToolsUpdater(w.ctx, entity)
worker = newToolsUpdater(w.ctx, entity, w.store)
if err := worker.Start(); err != nil {
slog.ErrorContext(w.ctx, "starting tools updater", "error", err)
return

View file

@ -25,16 +25,18 @@ import (
commonParams "github.com/cloudbase/garm-provider-common/params"
"github.com/cloudbase/garm/cache"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
garmUtil "github.com/cloudbase/garm/util"
"github.com/cloudbase/garm/util/github"
)
func newToolsUpdater(ctx context.Context, entity params.ForgeEntity) *toolsUpdater {
func newToolsUpdater(ctx context.Context, entity params.ForgeEntity, store common.Store) *toolsUpdater {
return &toolsUpdater{
ctx: ctx,
entity: entity,
quit: make(chan struct{}),
store: store,
}
}
@ -44,6 +46,7 @@ type toolsUpdater struct {
entity params.ForgeEntity
tools []commonParams.RunnerApplicationDownload
lastUpdate time.Time
store common.Store
mux sync.Mutex
running bool
@ -157,7 +160,14 @@ func (t *toolsUpdater) giteaUpdateLoop() {
}
t.sleepWithCancel(time.Duration(randInt.Int64()) * time.Millisecond)
tools, err := getTools()
if err == nil {
if err != nil {
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventError, fmt.Sprintf("failed to update gitea tools: %q", err), 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
} else {
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventInfo, "successfully updated tools", 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
cache.SetGithubToolsCache(t.entity, tools)
}
@ -174,9 +184,15 @@ func (t *toolsUpdater) giteaUpdateLoop() {
case <-ticker.C:
tools, err := getTools()
if err != nil {
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventError, fmt.Sprintf("failed to update gitea tools: %q", err), 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
slog.DebugContext(t.ctx, "failed to update gitea tools", "error", err)
continue
}
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventInfo, "successfully updated tools", 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
cache.SetGithubToolsCache(t.entity, tools)
}
}
@ -197,12 +213,18 @@ func (t *toolsUpdater) loop() {
now := time.Now().UTC()
if now.After(t.lastUpdate.Add(40 * time.Minute)) {
if err := t.updateTools(); err != nil {
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventError, fmt.Sprintf("failed to update tools: %q", err), 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
slog.ErrorContext(t.ctx, "initial tools update error", "error", err)
resetTime = now.Add(5 * time.Minute)
slog.ErrorContext(t.ctx, "initial tools update error", "error", err)
} else {
// Tools are usually valid for 1 hour.
resetTime = t.lastUpdate.Add(40 * time.Minute)
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventInfo, "successfully updated tools", 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
}
}
@ -224,12 +246,18 @@ func (t *toolsUpdater) loop() {
case <-timer.C:
slog.DebugContext(t.ctx, "updating tools")
now = time.Now().UTC()
if err := t.updateTools(); err == nil {
if err := t.updateTools(); err != nil {
slog.ErrorContext(t.ctx, "updating tools", "error", err)
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventError, fmt.Sprintf("failed to update tools: %q", err), 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
resetTime = now.Add(5 * time.Minute)
} else {
// Tools are usually valid for 1 hour.
resetTime = t.lastUpdate.Add(40 * time.Minute)
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventInfo, "successfully updated tools", 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
}
case <-t.reset:
slog.DebugContext(t.ctx, "resetting tools updater")
@ -237,10 +265,16 @@ func (t *toolsUpdater) loop() {
now = time.Now().UTC()
if err := t.updateTools(); err != nil {
slog.ErrorContext(t.ctx, "updating tools", "error", err)
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventError, fmt.Sprintf("failed to update tools: %q", err), 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
resetTime = now.Add(5 * time.Minute)
} else {
// Tools are usually valid for 1 hour.
resetTime = t.lastUpdate.Add(40 * time.Minute)
if err := t.store.AddEntityEvent(t.ctx, t.entity, params.StatusEvent, params.EventInfo, "successfully updated tools", 30); err != nil {
slog.ErrorContext(t.ctx, "failed to add entity event", "error", err)
}
}
}
timer.Stop()