Make the default github endpoint mutable

This change allows users to remove the default github endpoint
if no credentials are set on it.

A new protection is added on URLs of any endpoint that prevents their
update if the endpoint has credentials set.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-05-24 21:10:05 +00:00
parent b5bd373061
commit 87055f23da
5 changed files with 197 additions and 28 deletions

View file

@ -94,6 +94,17 @@ func (s *sqlDatabase) UpdateGiteaEndpoint(_ context.Context, name string, param
}
return errors.Wrap(err, "fetching gitea endpoint")
}
var credsCount int64
if err := tx.Model(&GiteaCredentials{}).Where("endpoint_name = ?", endpoint.Name).Count(&credsCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching gitea credentials")
}
}
if credsCount > 0 && (param.APIBaseURL != nil || param.BaseURL != nil) {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update endpoint URLs with existing credentials")
}
if param.APIBaseURL != nil {
endpoint.APIBaseURL = *param.APIBaseURL
}
@ -140,10 +151,6 @@ func (s *sqlDatabase) GetGiteaEndpoint(_ context.Context, name string) (params.F
}
func (s *sqlDatabase) DeleteGiteaEndpoint(_ context.Context, name string) (err error) {
if name == defaultGithubEndpoint {
return runnerErrors.NewBadRequestError("cannot delete default endpoint %s", defaultGithubEndpoint)
}
defer func() {
if err == nil {
s.sendNotify(common.GithubEndpointEntityType, common.DeleteOperation, params.ForgeEndpoint{Name: name})
@ -180,7 +187,7 @@ func (s *sqlDatabase) DeleteGiteaEndpoint(_ context.Context, name string) (err e
}
if credsCount > 0 || repoCnt > 0 || orgCnt > 0 {
return runnerErrors.NewBadRequestError("cannot delete endpoint with associated entities")
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete endpoint with associated entities")
}
if err := tx.Unscoped().Delete(&endpoint).Error; err != nil {

View file

@ -162,8 +162,8 @@ func (s *GiteaTestSuite) TestUpdateEndpoint() {
s.Require().NotNil(endpoint)
newDescription := "another description"
newAPIBaseURL := "https://new-api.example.com"
newBaseURL := "https://new.example.com"
newAPIBaseURL := "https://updated.example.com"
newBaseURL := "https://updated.example.com"
caCertBundle, err := os.ReadFile("../../testdata/certs/srv-pub.pem")
s.Require().NoError(err)
updateEpParams := params.UpdateGiteaEndpointParams{
@ -770,6 +770,61 @@ func (s *GiteaTestSuite) TestDeleteGiteaEndpointFailsWithOrgsReposOrCredentials(
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GiteaTestSuite) TestUpdateEndpointURLsFailsIfCredentialsAreAssociated() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGiteaEndpointParams{
Name: "deleteme",
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
BaseURL: testBaseURL,
}
endpoint, err := s.db.CreateGiteaEndpoint(ctx, createEpParams)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
credParams := params.CreateGiteaCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: testEndpointName,
AuthType: params.ForgeAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
_, err = s.db.CreateGiteaCredentials(ctx, credParams)
s.Require().NoError(err)
newDescription := "new gitea description"
newBaseURL := "https://new-gitea.example.com"
newAPIBaseURL := "https://new-gotea.example.com"
updateEpParams := params.UpdateGiteaEndpointParams{
BaseURL: &newBaseURL,
}
_, err = s.db.UpdateGiteaEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating gitea endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGiteaEndpointParams{
APIBaseURL: &newAPIBaseURL,
}
_, err = s.db.UpdateGiteaEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating gitea endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGiteaEndpointParams{
Description: &newDescription,
}
ret, err := s.db.UpdateGiteaEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().NoError(err)
s.Require().Equal(newDescription, ret.Description)
}
func (s *GiteaTestSuite) TestListGiteaEndpoints() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())

View file

@ -26,10 +26,6 @@ import (
"github.com/cloudbase/garm/params"
)
const (
defaultGithubEndpoint string = "github.com"
)
func (s *sqlDatabase) CreateGithubEndpoint(_ context.Context, param params.CreateGithubEndpointParams) (ghEndpoint params.ForgeEndpoint, err error) {
defer func() {
if err == nil {
@ -85,10 +81,6 @@ func (s *sqlDatabase) ListGithubEndpoints(_ context.Context) ([]params.ForgeEndp
}
func (s *sqlDatabase) UpdateGithubEndpoint(_ context.Context, name string, param params.UpdateGithubEndpointParams) (ghEndpoint params.ForgeEndpoint, err error) {
if name == defaultGithubEndpoint {
return params.ForgeEndpoint{}, errors.Wrap(runnerErrors.ErrBadRequest, "cannot update default github endpoint")
}
defer func() {
if err == nil {
s.sendNotify(common.GithubEndpointEntityType, common.UpdateOperation, ghEndpoint)
@ -102,6 +94,17 @@ func (s *sqlDatabase) UpdateGithubEndpoint(_ context.Context, name string, param
}
return errors.Wrap(err, "fetching github endpoint")
}
var credsCount int64
if err := tx.Model(&GithubCredentials{}).Where("endpoint_name = ?", endpoint.Name).Count(&credsCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github credentials")
}
}
if credsCount > 0 && (param.APIBaseURL != nil || param.BaseURL != nil || param.UploadBaseURL != nil) {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update endpoint URLs with existing credentials")
}
if param.APIBaseURL != nil {
endpoint.APIBaseURL = *param.APIBaseURL
}
@ -153,10 +156,6 @@ func (s *sqlDatabase) GetGithubEndpoint(_ context.Context, name string) (params.
}
func (s *sqlDatabase) DeleteGithubEndpoint(_ context.Context, name string) (err error) {
if name == defaultGithubEndpoint {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete default github endpoint")
}
defer func() {
if err == nil {
s.sendNotify(common.GithubEndpointEntityType, common.DeleteOperation, params.ForgeEndpoint{Name: name})
@ -200,7 +199,7 @@ func (s *sqlDatabase) DeleteGithubEndpoint(_ context.Context, name string) (err
}
if credsCount > 0 || repoCnt > 0 || orgCnt > 0 || entCnt > 0 {
return runnerErrors.NewBadRequestError("cannot delete endpoint with associated entities")
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete endpoint with associated entities")
}
if err := tx.Unscoped().Delete(&endpoint).Error; err != nil {

View file

@ -41,6 +41,7 @@ const (
testEndpointDescription string = "test description"
testCredsName string = "test-creds"
testCredsDescription string = "test creds"
defaultGithubEndpoint string = "github.com"
)
type GithubTestSuite struct {
@ -57,18 +58,17 @@ func (s *GithubTestSuite) SetupTest() {
s.db = db
}
func (s *GithubTestSuite) TestDefaultEndpointGetsCreatedAutomatically() {
func (s *GithubTestSuite) TestDefaultEndpointGetsCreatedAutomaticallyIfNoOtherEndpointExists() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
endpoint, err := s.db.GetGithubEndpoint(ctx, defaultGithubEndpoint)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
}
func (s *GithubTestSuite) TestDeletingDefaultEndpointFails() {
func (s *GithubTestSuite) TestDeletingDefaultEndpointWorksIfNoCredentials() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
err := s.db.DeleteGithubEndpoint(ctx, defaultGithubEndpoint)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().NoError(err)
}
func (s *GithubTestSuite) TestCreatingEndpoint() {
@ -154,6 +154,39 @@ func (s *GithubTestSuite) TestDeletingEndpoint() {
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
}
func (s *GithubTestSuite) TestDeleteGithubEndpointFailsWhenCredentialsExist() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGithubEndpointParams{
Name: testEndpointName,
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
}
endpoint, err := s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: testEndpointName,
AuthType: params.ForgeAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
_, err = s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
err = s.db.DeleteGithubEndpoint(ctx, testEndpointName)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
}
func (s *GithubTestSuite) TestUpdateEndpoint() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
@ -169,7 +202,7 @@ func (s *GithubTestSuite) TestUpdateEndpoint() {
s.Require().NoError(err)
s.Require().NotNil(endpoint)
newDescription := "new description"
newDescription := "the new description"
newAPIBaseURL := "https://new-api.example.com"
newUploadBaseURL := "https://new-uploads.example.com"
newBaseURL := "https://new.example.com"
@ -193,6 +226,72 @@ func (s *GithubTestSuite) TestUpdateEndpoint() {
s.Require().Equal(caCertBundle, updatedEndpoint.CACertBundle)
}
func (s *GithubTestSuite) TestUpdateEndpointURLsFailsIfCredentialsAreAssociated() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
createEpParams := params.CreateGithubEndpointParams{
Name: testEndpointName,
Description: testEndpointDescription,
APIBaseURL: testAPIBaseURL,
UploadBaseURL: testUploadBaseURL,
BaseURL: testBaseURL,
}
endpoint, err := s.db.CreateGithubEndpoint(ctx, createEpParams)
s.Require().NoError(err)
s.Require().NotNil(endpoint)
credParams := params.CreateGithubCredentialsParams{
Name: testCredsName,
Description: testCredsDescription,
Endpoint: testEndpointName,
AuthType: params.ForgeAuthTypePAT,
PAT: params.GithubPAT{
OAuth2Token: "test",
},
}
_, err = s.db.CreateGithubCredentials(ctx, credParams)
s.Require().NoError(err)
newDescription := "new description"
newBaseURL := "https://new.example.com"
newAPIBaseURL := "https://new-api.example.com"
newUploadBaseURL := "https://new-uploads.example.com"
updateEpParams := params.UpdateGithubEndpointParams{
BaseURL: &newBaseURL,
}
_, err = s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGithubEndpointParams{
UploadBaseURL: &newUploadBaseURL,
}
_, err = s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGithubEndpointParams{
APIBaseURL: &newAPIBaseURL,
}
_, err = s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGithubEndpointParams{
Description: &newDescription,
}
ret, err := s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().NoError(err)
s.Require().Equal(newDescription, ret.Description)
}
func (s *GithubTestSuite) TestUpdatingNonExistingEndpointReturnsNotFoundError() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())

View file

@ -218,9 +218,18 @@ func (s *sqlDatabase) ensureGithubEndpoint() error {
UploadBaseURL: appdefaults.GithubDefaultUploadBaseURL,
}
if _, err := s.CreateGithubEndpoint(context.Background(), createEndpointParams); err != nil {
if !errors.Is(err, runnerErrors.ErrDuplicateEntity) {
return errors.Wrap(err, "creating default github endpoint")
var epCount int64
if err := s.conn.Model(&GithubEndpoint{}).Count(&epCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "counting github endpoints")
}
}
if epCount == 0 {
if _, err := s.CreateGithubEndpoint(context.Background(), createEndpointParams); err != nil {
if !errors.Is(err, runnerErrors.ErrDuplicateEntity) {
return errors.Wrap(err, "creating default github endpoint")
}
}
}