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:
130
internal/models/registry_credential.go
Normal file
130
internal/models/registry_credential.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"sneak.berlin/go/upaas/internal/database"
|
||||
)
|
||||
|
||||
// RegistryCredential represents authentication credentials for a private Docker registry.
|
||||
type RegistryCredential struct {
|
||||
db *database.Database
|
||||
|
||||
ID int64
|
||||
AppID string
|
||||
Registry string
|
||||
Username string
|
||||
Password string //nolint:gosec // credential field required for registry auth
|
||||
}
|
||||
|
||||
// NewRegistryCredential creates a new RegistryCredential with a database reference.
|
||||
func NewRegistryCredential(db *database.Database) *RegistryCredential {
|
||||
return &RegistryCredential{db: db}
|
||||
}
|
||||
|
||||
// Save inserts or updates the registry credential in the database.
|
||||
func (r *RegistryCredential) Save(ctx context.Context) error {
|
||||
if r.ID == 0 {
|
||||
return r.insert(ctx)
|
||||
}
|
||||
|
||||
return r.update(ctx)
|
||||
}
|
||||
|
||||
// Delete removes the registry credential from the database.
|
||||
func (r *RegistryCredential) Delete(ctx context.Context) error {
|
||||
_, err := r.db.Exec(ctx, "DELETE FROM registry_credentials WHERE id = ?", r.ID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RegistryCredential) insert(ctx context.Context) error {
|
||||
query := "INSERT INTO registry_credentials (app_id, registry, username, password) VALUES (?, ?, ?, ?)"
|
||||
|
||||
result, err := r.db.Exec(ctx, query, r.AppID, r.Registry, r.Username, r.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.ID = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RegistryCredential) update(ctx context.Context) error {
|
||||
query := "UPDATE registry_credentials SET registry = ?, username = ?, password = ? WHERE id = ?"
|
||||
|
||||
_, err := r.db.Exec(ctx, query, r.Registry, r.Username, r.Password, r.ID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// FindRegistryCredential finds a registry credential by ID.
|
||||
//
|
||||
//nolint:nilnil // returning nil,nil is idiomatic for "not found" in Active Record
|
||||
func FindRegistryCredential(
|
||||
ctx context.Context,
|
||||
db *database.Database,
|
||||
id int64,
|
||||
) (*RegistryCredential, error) {
|
||||
cred := NewRegistryCredential(db)
|
||||
|
||||
row := db.QueryRow(ctx,
|
||||
"SELECT id, app_id, registry, username, password FROM registry_credentials WHERE id = ?",
|
||||
id,
|
||||
)
|
||||
|
||||
err := row.Scan(&cred.ID, &cred.AppID, &cred.Registry, &cred.Username, &cred.Password)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("scanning registry credential: %w", err)
|
||||
}
|
||||
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
// FindRegistryCredentialsByAppID finds all registry credentials for an app.
|
||||
func FindRegistryCredentialsByAppID(
|
||||
ctx context.Context,
|
||||
db *database.Database,
|
||||
appID string,
|
||||
) ([]*RegistryCredential, error) {
|
||||
query := `
|
||||
SELECT id, app_id, registry, username, password FROM registry_credentials
|
||||
WHERE app_id = ? ORDER BY registry`
|
||||
|
||||
rows, err := db.Query(ctx, query, appID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying registry credentials by app: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var creds []*RegistryCredential
|
||||
|
||||
for rows.Next() {
|
||||
cred := NewRegistryCredential(db)
|
||||
|
||||
scanErr := rows.Scan(
|
||||
&cred.ID, &cred.AppID, &cred.Registry, &cred.Username, &cred.Password,
|
||||
)
|
||||
if scanErr != nil {
|
||||
return nil, scanErr
|
||||
}
|
||||
|
||||
creds = append(creds, cred)
|
||||
}
|
||||
|
||||
return creds, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user