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:
parent
c13fd8c746
commit
5fb0b111fc
1
go.mod
1
go.mod
@ -44,6 +44,7 @@ require (
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/morikuni/aec v1.1.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
|
||||
3
go.sum
3
go.sum
@ -86,10 +86,13 @@ github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
|
||||
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
||||
44
internal/database/migrations/002_remove_container_id.sql
Normal file
44
internal/database/migrations/002_remove_container_id.sql
Normal file
@ -0,0 +1,44 @@
|
||||
-- Remove container_id from apps table
|
||||
-- Container is now looked up via Docker label (upaas.id) instead of stored in database
|
||||
|
||||
-- SQLite doesn't support DROP COLUMN before version 3.35.0 (2021-03-12)
|
||||
-- Use table rebuild for broader compatibility
|
||||
|
||||
-- Create new table without container_id
|
||||
CREATE TABLE apps_new (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
repo_url TEXT NOT NULL,
|
||||
branch TEXT NOT NULL DEFAULT 'main',
|
||||
dockerfile_path TEXT DEFAULT 'Dockerfile',
|
||||
webhook_secret TEXT NOT NULL,
|
||||
ssh_private_key TEXT NOT NULL,
|
||||
ssh_public_key TEXT NOT NULL,
|
||||
image_id TEXT,
|
||||
status TEXT DEFAULT 'pending',
|
||||
docker_network TEXT,
|
||||
ntfy_topic TEXT,
|
||||
slack_webhook TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Copy data (excluding container_id)
|
||||
INSERT INTO apps_new (
|
||||
id, name, repo_url, branch, dockerfile_path, webhook_secret,
|
||||
ssh_private_key, ssh_public_key, image_id, status,
|
||||
docker_network, ntfy_topic, slack_webhook, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id, name, repo_url, branch, dockerfile_path, webhook_secret,
|
||||
ssh_private_key, ssh_public_key, image_id, status,
|
||||
docker_network, ntfy_topic, slack_webhook, created_at, updated_at
|
||||
FROM apps;
|
||||
|
||||
-- Drop old table and rename new one
|
||||
DROP TABLE apps;
|
||||
ALTER TABLE apps_new RENAME TO apps;
|
||||
|
||||
-- Recreate indexes
|
||||
CREATE INDEX idx_apps_status ON apps(status);
|
||||
CREATE INDEX idx_apps_webhook_secret ON apps(webhook_secret);
|
||||
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
@ -309,6 +310,51 @@ func (c *Client) IsContainerHealthy(
|
||||
return inspect.State.Health.Status == "healthy", nil
|
||||
}
|
||||
|
||||
// LabelUpaasID is the Docker label key used to identify containers managed by upaas.
|
||||
const LabelUpaasID = "upaas.id"
|
||||
|
||||
// ContainerInfo contains basic information about a container.
|
||||
type ContainerInfo struct {
|
||||
ID string
|
||||
Running bool
|
||||
}
|
||||
|
||||
// FindContainerByAppID finds a container by the upaas.id label.
|
||||
// Returns nil if no container is found.
|
||||
//
|
||||
//nolint:nilnil // returning nil,nil is idiomatic for "not found"
|
||||
func (c *Client) FindContainerByAppID(
|
||||
ctx context.Context,
|
||||
appID string,
|
||||
) (*ContainerInfo, error) {
|
||||
if c.docker == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
|
||||
filterArgs := filters.NewArgs()
|
||||
filterArgs.Add("label", LabelUpaasID+"="+appID)
|
||||
|
||||
containers, err := c.docker.ContainerList(ctx, container.ListOptions{
|
||||
All: true,
|
||||
Filters: filterArgs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list containers: %w", err)
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Return the first matching container
|
||||
ctr := containers[0]
|
||||
|
||||
return &ContainerInfo{
|
||||
ID: ctr.ID,
|
||||
Running: ctr.State == "running",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// cloneConfig holds configuration for a git clone operation.
|
||||
type cloneConfig struct {
|
||||
repoURL string
|
||||
|
||||
@ -348,7 +348,8 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc {
|
||||
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
||||
if !application.ContainerID.Valid {
|
||||
containerInfo, containerErr := h.docker.FindContainerByAppID(request.Context(), appID)
|
||||
if containerErr != nil || containerInfo == nil {
|
||||
_, _ = writer.Write([]byte("No container running\n"))
|
||||
|
||||
return
|
||||
@ -361,14 +362,14 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc {
|
||||
|
||||
logs, logsErr := h.docker.ContainerLogs(
|
||||
request.Context(),
|
||||
application.ContainerID.String,
|
||||
containerInfo.ID,
|
||||
tail,
|
||||
)
|
||||
if logsErr != nil {
|
||||
h.log.Error("failed to get container logs",
|
||||
"error", logsErr,
|
||||
"app", application.Name,
|
||||
"container", application.ContainerID.String,
|
||||
"container", containerInfo.ID,
|
||||
)
|
||||
|
||||
_, _ = writer.Write([]byte("Failed to fetch container logs\n"))
|
||||
@ -396,22 +397,23 @@ func (h *Handlers) handleContainerAction(
|
||||
action containerAction,
|
||||
) {
|
||||
appID := chi.URLParam(request, "id")
|
||||
ctx := request.Context()
|
||||
|
||||
application, findErr := models.FindApp(request.Context(), h.db, appID)
|
||||
application, findErr := models.FindApp(ctx, h.db, appID)
|
||||
if findErr != nil || application == nil {
|
||||
http.NotFound(writer, request)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !application.ContainerID.Valid {
|
||||
containerInfo, containerErr := h.docker.FindContainerByAppID(ctx, appID)
|
||||
if containerErr != nil || containerInfo == nil {
|
||||
http.Redirect(writer, request, "/apps/"+appID, http.StatusSeeOther)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
containerID := application.ContainerID.String
|
||||
ctx := request.Context()
|
||||
containerID := containerInfo.ID
|
||||
|
||||
var actionErr error
|
||||
|
||||
|
||||
@ -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`,
|
||||
)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -122,7 +122,6 @@
|
||||
<!-- Labels -->
|
||||
<div class="card p-6 mb-6">
|
||||
<h2 class="section-title mb-4">Docker Labels</h2>
|
||||
{{if .Labels}}
|
||||
<div class="overflow-x-auto mb-4">
|
||||
<table class="table">
|
||||
<thead class="table-header">
|
||||
@ -133,6 +132,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-body">
|
||||
<!-- System-managed upaas.id label -->
|
||||
<tr class="bg-gray-50">
|
||||
<td class="font-mono font-medium text-gray-600">upaas.id</td>
|
||||
<td class="font-mono text-gray-500">{{.App.ID}}</td>
|
||||
<td class="text-right">
|
||||
<span class="text-xs text-gray-400">System</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{range .Labels}}
|
||||
<tr>
|
||||
<td class="font-mono font-medium">{{.Key}}</td>
|
||||
@ -147,7 +154,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
<form method="POST" action="/apps/{{.App.ID}}/labels" class="flex flex-col sm:flex-row gap-2">
|
||||
<input type="text" name="key" placeholder="label.key" required class="input flex-1 font-mono text-sm">
|
||||
<input type="text" name="value" placeholder="value" required class="input flex-1 font-mono text-sm">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user