Check / check (pull_request) Skipped
App names may now contain dots, such as sneak.berlin: runs of lowercase letters and numbers joined by single dots or by hyphens, 2 to 63 characters. Docker accepts every such name in the app's image name, upaas-<name>; a dot needs a letter or number on both sides because Docker requires it. The rule also keeps a dot off either end, so the name is safe as a directory and log file name. The new and edit app forms use the same pattern. Their old one was not a valid regular expression under the flag browsers compile it with, so browsers ignored it. A test creates an app named sneak.berlin through the new app form, then builds and deploys it against the fake Docker API and checks the image and container names with Docker's own rules. Model: opus-5-5
134 lines
3.3 KiB
Go
134 lines
3.3 KiB
Go
package deploy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"sneak.berlin/go/upaas/internal/config"
|
|
"sneak.berlin/go/upaas/internal/database"
|
|
"sneak.berlin/go/upaas/internal/docker"
|
|
"sneak.berlin/go/upaas/internal/models"
|
|
)
|
|
|
|
// NewTestService creates a Service with minimal dependencies for testing.
|
|
func NewTestService(log *slog.Logger) *Service {
|
|
return &Service{
|
|
log: log,
|
|
}
|
|
}
|
|
|
|
// CancelActiveDeploy exposes cancelActiveDeploy for testing.
|
|
func (svc *Service) CancelActiveDeploy(appID string) {
|
|
svc.cancelActiveDeploy(appID)
|
|
}
|
|
|
|
// RegisterActiveDeploy registers an active deploy for testing.
|
|
func (svc *Service) RegisterActiveDeploy(
|
|
appID string,
|
|
cancel context.CancelFunc,
|
|
done chan struct{},
|
|
) {
|
|
svc.activeDeploys.Store(appID, &activeDeploy{cancel: cancel, done: done})
|
|
}
|
|
|
|
// TryLockApp exposes tryLockApp for testing.
|
|
func (svc *Service) TryLockApp(appID string) bool {
|
|
return svc.tryLockApp(appID)
|
|
}
|
|
|
|
// UnlockApp exposes unlockApp for testing.
|
|
func (svc *Service) UnlockApp(appID string) {
|
|
svc.unlockApp(appID)
|
|
}
|
|
|
|
// NewTestServiceWithConfig creates a Service with config, database and
|
|
// docker client for testing.
|
|
func NewTestServiceWithConfig(
|
|
log *slog.Logger,
|
|
cfg *config.Config,
|
|
db *database.Database,
|
|
dockerClient *docker.Client,
|
|
) *Service {
|
|
return &Service{
|
|
log: log,
|
|
config: cfg,
|
|
db: db,
|
|
docker: dockerClient,
|
|
}
|
|
}
|
|
|
|
// CleanupCancelledDeploy exposes the build directory cleanup portion of
|
|
// cleanupCancelledDeploy for testing. It removes build directories matching
|
|
// the deployment ID prefix.
|
|
func (svc *Service) CleanupCancelledDeploy(
|
|
_ context.Context,
|
|
appName string,
|
|
deploymentID int64,
|
|
_ string,
|
|
) {
|
|
// We can't create real models.App/Deployment in tests easily,
|
|
// so we test the build dir cleanup portion directly.
|
|
buildDir := svc.GetBuildDir(appName)
|
|
|
|
entries, err := os.ReadDir(buildDir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
prefix := fmt.Sprintf("%d-", deploymentID)
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() && strings.HasPrefix(entry.Name(), prefix) {
|
|
dirPath := filepath.Join(buildDir, entry.Name())
|
|
_ = os.RemoveAll(dirPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetBuildDirExported exposes GetBuildDir for testing.
|
|
func (svc *Service) GetBuildDirExported(appName string) string {
|
|
return svc.GetBuildDir(appName)
|
|
}
|
|
|
|
// RecordDeployedImage exposes recordDeployedImage for testing.
|
|
func (svc *Service) RecordDeployedImage(
|
|
ctx context.Context,
|
|
app *models.App,
|
|
deployment *models.Deployment,
|
|
imageID docker.ImageID,
|
|
) error {
|
|
return svc.recordDeployedImage(ctx, app, deployment, imageID)
|
|
}
|
|
|
|
// BuildImage exposes buildImage for testing.
|
|
func (svc *Service) BuildImage(
|
|
ctx context.Context,
|
|
app *models.App,
|
|
deployment *models.Deployment,
|
|
) (docker.ImageID, error) {
|
|
return svc.buildImage(ctx, app, deployment)
|
|
}
|
|
|
|
// DeployContainer exposes deployContainerWithTimeout for testing.
|
|
func (svc *Service) DeployContainer(
|
|
ctx context.Context,
|
|
app *models.App,
|
|
deployment *models.Deployment,
|
|
imageID docker.ImageID,
|
|
) error {
|
|
return svc.deployContainerWithTimeout(ctx, app, deployment, imageID)
|
|
}
|
|
|
|
// BuildContainerOptionsExported exposes buildContainerOptions for testing.
|
|
func (svc *Service) BuildContainerOptionsExported(
|
|
ctx context.Context,
|
|
app *models.App,
|
|
imageID docker.ImageID,
|
|
) (docker.CreateContainerOptions, error) {
|
|
return svc.buildContainerOptions(ctx, app, imageID)
|
|
}
|