Use ULID for app IDs and Docker label for container lookup
- Replace UUID with ULID for app ID generation (lexicographically sortable) - Remove container_id column from apps table (migration 002) - Add upaas.id Docker label to identify containers by app ID - Implement FindContainerByAppID in Docker client to query by label - Update handlers and deploy service to use label-based container lookup - Show system-managed upaas.id label in UI with editing disabled Container association is now determined dynamically via Docker label rather than stored in the database, making the system more resilient to container recreation or external changes.
This commit is contained in:
@@ -3,11 +3,14 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.eeqj.de/sneak/upaas/internal/database"
|
||||
@@ -62,9 +65,9 @@ func (svc *Service) CreateApp(
|
||||
return nil, fmt.Errorf("failed to generate SSH key pair: %w", err)
|
||||
}
|
||||
|
||||
// Create app
|
||||
// Create app with ULID
|
||||
app := models.NewApp(svc.db)
|
||||
app.ID = uuid.New().String()
|
||||
app.ID = ulid.MustNew(ulid.Timestamp(time.Now()), rand.Reader).String()
|
||||
app.Name = input.Name
|
||||
app.RepoURL = input.RepoURL
|
||||
|
||||
@@ -324,20 +327,3 @@ func (svc *Service) UpdateAppStatus(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAppContainer updates the container ID of an app.
|
||||
func (svc *Service) UpdateAppContainer(
|
||||
ctx context.Context,
|
||||
app *models.App,
|
||||
containerID, imageID string,
|
||||
) error {
|
||||
app.ContainerID = sql.NullString{String: containerID, Valid: containerID != ""}
|
||||
app.ImageID = sql.NullString{String: imageID, Valid: imageID != ""}
|
||||
|
||||
saveErr := app.Save(ctx)
|
||||
if saveErr != nil {
|
||||
return fmt.Errorf("failed to save app container: %w", saveErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -600,37 +600,3 @@ func TestUpdateAppStatus(testingT *testing.T) {
|
||||
assert.Equal(t, models.AppStatusBuilding, reloaded.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateAppContainer(testingT *testing.T) {
|
||||
testingT.Parallel()
|
||||
|
||||
testingT.Run("updates container and image IDs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc, cleanup := setupTestService(t)
|
||||
defer cleanup()
|
||||
|
||||
createdApp, err := svc.CreateApp(context.Background(), app.CreateAppInput{
|
||||
Name: "container-test",
|
||||
RepoURL: "git@example.com:user/repo.git",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, createdApp.ContainerID.Valid)
|
||||
assert.False(t, createdApp.ImageID.Valid)
|
||||
|
||||
err = svc.UpdateAppContainer(
|
||||
context.Background(),
|
||||
createdApp,
|
||||
"container123",
|
||||
"image456",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
reloaded, err := svc.GetApp(context.Background(), createdApp.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, reloaded.ContainerID.Valid)
|
||||
assert.Equal(t, "container123", reloaded.ContainerID.String)
|
||||
assert.True(t, reloaded.ImageID.Valid)
|
||||
assert.Equal(t, "image456", reloaded.ImageID.String)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
const (
|
||||
healthCheckDelaySeconds = 60
|
||||
// upaasLabelCount is the number of upaas-specific labels added to containers.
|
||||
upaasLabelCount = 2
|
||||
upaasLabelCount = 1
|
||||
)
|
||||
|
||||
// Sentinel errors for deployment failures.
|
||||
@@ -104,12 +104,12 @@ func (svc *Service) Deploy(
|
||||
|
||||
svc.removeOldContainer(ctx, app, deployment)
|
||||
|
||||
containerID, err := svc.createAndStartContainer(ctx, app, deployment, imageID)
|
||||
_, err = svc.createAndStartContainer(ctx, app, deployment, imageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.updateAppRunning(ctx, app, containerID, imageID)
|
||||
err = svc.updateAppRunning(ctx, app, imageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -243,13 +243,14 @@ func (svc *Service) removeOldContainer(
|
||||
app *models.App,
|
||||
deployment *models.Deployment,
|
||||
) {
|
||||
if !app.ContainerID.Valid || app.ContainerID.String == "" {
|
||||
containerInfo, err := svc.docker.FindContainerByAppID(ctx, app.ID)
|
||||
if err != nil || containerInfo == nil {
|
||||
return
|
||||
}
|
||||
|
||||
svc.log.Info("removing old container", "id", app.ContainerID.String)
|
||||
svc.log.Info("removing old container", "id", containerInfo.ID)
|
||||
|
||||
removeErr := svc.docker.RemoveContainer(ctx, app.ContainerID.String, true)
|
||||
removeErr := svc.docker.RemoveContainer(ctx, containerInfo.ID, true)
|
||||
if removeErr != nil {
|
||||
svc.log.Warn("failed to remove old container", "error", removeErr)
|
||||
}
|
||||
@@ -350,8 +351,8 @@ func buildLabelMap(app *models.App, labels []*models.Label) map[string]string {
|
||||
labelMap[label.Key] = label.Value
|
||||
}
|
||||
|
||||
labelMap["upaas.app.id"] = app.ID
|
||||
labelMap["upaas.app.name"] = app.Name
|
||||
// Add the upaas.id label to identify this container
|
||||
labelMap[docker.LabelUpaasID] = app.ID
|
||||
|
||||
return labelMap
|
||||
}
|
||||
@@ -372,9 +373,8 @@ func buildVolumeMounts(volumes []*models.Volume) []docker.VolumeMount {
|
||||
func (svc *Service) updateAppRunning(
|
||||
ctx context.Context,
|
||||
app *models.App,
|
||||
containerID, imageID string,
|
||||
imageID string,
|
||||
) error {
|
||||
app.ContainerID = sql.NullString{String: containerID, Valid: true}
|
||||
app.ImageID = sql.NullString{String: imageID, Valid: true}
|
||||
app.Status = models.AppStatusRunning
|
||||
|
||||
@@ -405,14 +405,12 @@ func (svc *Service) checkHealthAfterDelay(
|
||||
return
|
||||
}
|
||||
|
||||
if !reloadedApp.ContainerID.Valid {
|
||||
containerInfo, containerErr := svc.docker.FindContainerByAppID(ctx, app.ID)
|
||||
if containerErr != nil || containerInfo == nil {
|
||||
return
|
||||
}
|
||||
|
||||
healthy, err := svc.docker.IsContainerHealthy(
|
||||
ctx,
|
||||
reloadedApp.ContainerID.String,
|
||||
)
|
||||
healthy, err := svc.docker.IsContainerHealthy(ctx, containerInfo.ID)
|
||||
if err != nil {
|
||||
svc.log.Error("failed to check container health", "error", err)
|
||||
svc.notify.NotifyDeployFailed(ctx, reloadedApp, deployment, err)
|
||||
|
||||
Reference in New Issue
Block a user