feat: add private Docker registry authentication for base images
All checks were successful
Check / check (pull_request) Successful in 3m34s
All checks were successful
Check / check (pull_request) Successful in 3m34s
Add per-app registry credentials that are passed to Docker during image builds, allowing apps to use base images from private registries. - New registry_credentials table (migration 007) - RegistryCredential model with full CRUD operations - Docker client passes AuthConfigs to ImageBuild when credentials exist - Deploy service fetches app registry credentials before builds - Web UI section for managing registry credentials (add/edit/delete) - Comprehensive unit tests for model and auth config builder - README updated to list the feature
This commit is contained in:
@@ -23,6 +23,7 @@ const (
|
||||
testBranch = "main"
|
||||
testValue = "value"
|
||||
testEventType = "push"
|
||||
testUser = "user"
|
||||
)
|
||||
|
||||
func setupTestDB(t *testing.T) (*database.Database, func()) {
|
||||
@@ -704,6 +705,127 @@ func TestAppGetWebhookEvents(t *testing.T) {
|
||||
assert.Len(t, events, 1)
|
||||
}
|
||||
|
||||
// RegistryCredential Tests.
|
||||
|
||||
func TestRegistryCredentialCreateAndFind(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
app := createTestApp(t, testDB)
|
||||
|
||||
cred := models.NewRegistryCredential(testDB)
|
||||
cred.AppID = app.ID
|
||||
cred.Registry = "registry.example.com"
|
||||
cred.Username = "myuser"
|
||||
cred.Password = "mypassword"
|
||||
|
||||
err := cred.Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, cred.ID)
|
||||
|
||||
creds, err := models.FindRegistryCredentialsByAppID(
|
||||
context.Background(), testDB, app.ID,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, creds, 1)
|
||||
assert.Equal(t, "registry.example.com", creds[0].Registry)
|
||||
assert.Equal(t, "myuser", creds[0].Username)
|
||||
assert.Equal(t, "mypassword", creds[0].Password)
|
||||
}
|
||||
|
||||
func TestRegistryCredentialUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
app := createTestApp(t, testDB)
|
||||
|
||||
cred := models.NewRegistryCredential(testDB)
|
||||
cred.AppID = app.ID
|
||||
cred.Registry = "old.registry.com"
|
||||
cred.Username = "olduser"
|
||||
cred.Password = "oldpass"
|
||||
|
||||
err := cred.Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
cred.Registry = "new.registry.com"
|
||||
cred.Username = "newuser"
|
||||
cred.Password = "newpass"
|
||||
|
||||
err = cred.Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := models.FindRegistryCredential(context.Background(), testDB, cred.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, found)
|
||||
assert.Equal(t, "new.registry.com", found.Registry)
|
||||
assert.Equal(t, "newuser", found.Username)
|
||||
assert.Equal(t, "newpass", found.Password)
|
||||
}
|
||||
|
||||
func TestRegistryCredentialDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
app := createTestApp(t, testDB)
|
||||
|
||||
cred := models.NewRegistryCredential(testDB)
|
||||
cred.AppID = app.ID
|
||||
cred.Registry = "delete.registry.com"
|
||||
cred.Username = testUser
|
||||
cred.Password = "pass"
|
||||
|
||||
err := cred.Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cred.Delete(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
creds, err := models.FindRegistryCredentialsByAppID(
|
||||
context.Background(), testDB, app.ID,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, creds)
|
||||
}
|
||||
|
||||
func TestRegistryCredentialFindByIDNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
found, err := models.FindRegistryCredential(context.Background(), testDB, 99999)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, found)
|
||||
}
|
||||
|
||||
func TestAppGetRegistryCredentials(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
app := createTestApp(t, testDB)
|
||||
|
||||
cred := models.NewRegistryCredential(testDB)
|
||||
cred.AppID = app.ID
|
||||
cred.Registry = "ghcr.io"
|
||||
cred.Username = testUser
|
||||
cred.Password = "token"
|
||||
_ = cred.Save(context.Background())
|
||||
|
||||
creds, err := app.GetRegistryCredentials(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, creds, 1)
|
||||
assert.Equal(t, "ghcr.io", creds[0].Registry)
|
||||
}
|
||||
|
||||
// Cascade Delete Tests.
|
||||
|
||||
//nolint:funlen // Test function with many assertions - acceptable for integration tests
|
||||
@@ -749,6 +871,13 @@ func TestCascadeDelete(t *testing.T) {
|
||||
deploy.Status = models.DeploymentStatusSuccess
|
||||
_ = deploy.Save(context.Background())
|
||||
|
||||
regCred := models.NewRegistryCredential(testDB)
|
||||
regCred.AppID = app.ID
|
||||
regCred.Registry = "registry.example.com"
|
||||
regCred.Username = testUser
|
||||
regCred.Password = "pass"
|
||||
_ = regCred.Save(context.Background())
|
||||
|
||||
// Delete app.
|
||||
err := app.Delete(context.Background())
|
||||
require.NoError(t, err)
|
||||
@@ -778,6 +907,11 @@ func TestCascadeDelete(t *testing.T) {
|
||||
context.Background(), testDB, app.ID, 10,
|
||||
)
|
||||
assert.Empty(t, deployments)
|
||||
|
||||
regCreds, _ := models.FindRegistryCredentialsByAppID(
|
||||
context.Background(), testDB, app.ID,
|
||||
)
|
||||
assert.Empty(t, regCreds)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user