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:
2025-12-29 16:06:40 +07:00
parent c13fd8c746
commit 5fb0b111fc
11 changed files with 142 additions and 91 deletions

View File

@@ -34,7 +34,6 @@ type App struct {
WebhookSecret string
SSHPrivateKey string
SSHPublicKey string
ContainerID sql.NullString
ImageID sql.NullString
Status AppStatus
DockerNetwork sql.NullString
@@ -73,7 +72,7 @@ func (a *App) Delete(ctx context.Context) error {
func (a *App) Reload(ctx context.Context) error {
row := a.db.QueryRow(ctx, `
SELECT id, name, repo_url, branch, dockerfile_path, webhook_secret,
ssh_private_key, ssh_public_key, container_id, image_id, status,
ssh_private_key, ssh_public_key, image_id, status,
docker_network, ntfy_topic, slack_webhook, created_at, updated_at
FROM apps WHERE id = ?`,
a.ID,
@@ -131,13 +130,13 @@ func (a *App) insert(ctx context.Context) error {
query := `
INSERT INTO apps (
id, name, repo_url, branch, dockerfile_path, webhook_secret,
ssh_private_key, ssh_public_key, container_id, image_id, status,
ssh_private_key, ssh_public_key, image_id, status,
docker_network, ntfy_topic, slack_webhook
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err := a.db.Exec(ctx, query,
a.ID, a.Name, a.RepoURL, a.Branch, a.DockerfilePath, a.WebhookSecret,
a.SSHPrivateKey, a.SSHPublicKey, a.ContainerID, a.ImageID, a.Status,
a.SSHPrivateKey, a.SSHPublicKey, a.ImageID, a.Status,
a.DockerNetwork, a.NtfyTopic, a.SlackWebhook,
)
if err != nil {
@@ -151,14 +150,14 @@ func (a *App) update(ctx context.Context) error {
query := `
UPDATE apps SET
name = ?, repo_url = ?, branch = ?, dockerfile_path = ?,
container_id = ?, image_id = ?, status = ?,
image_id = ?, status = ?,
docker_network = ?, ntfy_topic = ?, slack_webhook = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?`
_, err := a.db.Exec(ctx, query,
a.Name, a.RepoURL, a.Branch, a.DockerfilePath,
a.ContainerID, a.ImageID, a.Status,
a.ImageID, a.Status,
a.DockerNetwork, a.NtfyTopic, a.SlackWebhook,
a.ID,
)
@@ -171,7 +170,7 @@ func (a *App) scan(row *sql.Row) error {
&a.ID, &a.Name, &a.RepoURL, &a.Branch,
&a.DockerfilePath, &a.WebhookSecret,
&a.SSHPrivateKey, &a.SSHPublicKey,
&a.ContainerID, &a.ImageID, &a.Status,
&a.ImageID, &a.Status,
&a.DockerNetwork, &a.NtfyTopic, &a.SlackWebhook,
&a.CreatedAt, &a.UpdatedAt,
)
@@ -187,7 +186,7 @@ func scanApps(appDB *database.Database, rows *sql.Rows) ([]*App, error) {
&app.ID, &app.Name, &app.RepoURL, &app.Branch,
&app.DockerfilePath, &app.WebhookSecret,
&app.SSHPrivateKey, &app.SSHPublicKey,
&app.ContainerID, &app.ImageID, &app.Status,
&app.ImageID, &app.Status,
&app.DockerNetwork, &app.NtfyTopic, &app.SlackWebhook,
&app.CreatedAt, &app.UpdatedAt,
)
@@ -219,7 +218,7 @@ func FindApp(
row := appDB.QueryRow(ctx, `
SELECT id, name, repo_url, branch, dockerfile_path, webhook_secret,
ssh_private_key, ssh_public_key, container_id, image_id, status,
ssh_private_key, ssh_public_key, image_id, status,
docker_network, ntfy_topic, slack_webhook, created_at, updated_at
FROM apps WHERE id = ?`,
appID,
@@ -249,7 +248,7 @@ func FindAppByWebhookSecret(
row := appDB.QueryRow(ctx, `
SELECT id, name, repo_url, branch, dockerfile_path, webhook_secret,
ssh_private_key, ssh_public_key, container_id, image_id, status,
ssh_private_key, ssh_public_key, image_id, status,
docker_network, ntfy_topic, slack_webhook, created_at, updated_at
FROM apps WHERE webhook_secret = ?`,
secret,
@@ -271,7 +270,7 @@ func FindAppByWebhookSecret(
func AllApps(ctx context.Context, appDB *database.Database) ([]*App, error) {
rows, err := appDB.Query(ctx, `
SELECT id, name, repo_url, branch, dockerfile_path, webhook_secret,
ssh_private_key, ssh_public_key, container_id, image_id, status,
ssh_private_key, ssh_public_key, image_id, status,
docker_network, ntfy_topic, slack_webhook, created_at, updated_at
FROM apps ORDER BY name`,
)

View File

@@ -223,7 +223,7 @@ func TestAppUpdate(t *testing.T) {
app.Name = "updated"
app.Status = models.AppStatusRunning
app.ContainerID = sql.NullString{String: "container123", Valid: true}
app.ImageID = sql.NullString{String: "image123", Valid: true}
err := app.Save(context.Background())
require.NoError(t, err)
@@ -232,7 +232,7 @@ func TestAppUpdate(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "updated", found.Name)
assert.Equal(t, models.AppStatusRunning, found.Status)
assert.Equal(t, "container123", found.ContainerID.String)
assert.Equal(t, "image123", found.ImageID.String)
}
func TestAppDelete(t *testing.T) {