feat(master): show built-from-source services but never update them (#7)
This commit is contained in:
@@ -76,6 +76,8 @@ func (s *Service) Updatable(svc model.Service) error {
|
|||||||
return &model.NotUpdatableError{Reason: model.ReasonRemoved}
|
return &model.NotUpdatableError{Reason: model.ReasonRemoved}
|
||||||
case svc.Self || imageref.Parse(svc.ActualRef).Repo == s.selfRepo:
|
case svc.Self || imageref.Parse(svc.ActualRef).Repo == s.selfRepo:
|
||||||
return &model.NotUpdatableError{Reason: model.ReasonSelf}
|
return &model.NotUpdatableError{Reason: model.ReasonSelf}
|
||||||
|
case svc.Built:
|
||||||
|
return &model.NotUpdatableError{Reason: model.ReasonBuilt}
|
||||||
case svc.Kind == protocol.KindContainer:
|
case svc.Kind == protocol.KindContainer:
|
||||||
return &model.NotUpdatableError{Reason: model.ReasonContainer}
|
return &model.NotUpdatableError{Reason: model.ReasonContainer}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ func TestUpdatable(t *testing.T) {
|
|||||||
{"flagged self", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, model.ReasonSelf},
|
{"flagged self", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, model.ReasonSelf},
|
||||||
{"same repo as the agent image", model.Service{Kind: protocol.KindCompose,
|
{"same repo as the agent image", model.Service{Kind: protocol.KindCompose,
|
||||||
ActualRef: "gitea.shcizo.se/shcizo/package-updater:1.2.0@sha256:abc"}, model.ReasonSelf},
|
ActualRef: "gitea.shcizo.se/shcizo/package-updater:1.2.0@sha256:abc"}, model.ReasonSelf},
|
||||||
|
{"built from source", model.Service{Kind: protocol.KindCompose, ActualRef: "slusk:1", Built: true}, model.ReasonBuilt},
|
||||||
{"container", model.Service{Kind: protocol.KindContainer, ActualRef: "redis:7"}, model.ReasonContainer},
|
{"container", model.Service{Kind: protocol.KindContainer, ActualRef: "redis:7"}, model.ReasonContainer},
|
||||||
{"digest without tag", model.Service{Kind: protocol.KindCompose, ActualRef: "redis@sha256:abc"}, model.ReasonNoTag},
|
{"digest without tag", model.Service{Kind: protocol.KindCompose, ActualRef: "redis@sha256:abc"}, model.ReasonNoTag},
|
||||||
{"bare image means latest", model.Service{Kind: protocol.KindCompose, ActualRef: "postgres"}, ""},
|
{"bare image means latest", model.Service{Kind: protocol.KindCompose, ActualRef: "postgres"}, ""},
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const (
|
|||||||
ReasonContainer = "plain containers cannot be updated"
|
ReasonContainer = "plain containers cannot be updated"
|
||||||
ReasonNoTag = "follows no tag"
|
ReasonNoTag = "follows no tag"
|
||||||
ReasonRemoved = "service no longer exists"
|
ReasonRemoved = "service no longer exists"
|
||||||
|
ReasonBuilt = "built from source (build:)"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JobStatus is the state of an update job.
|
// JobStatus is the state of an update job.
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ type Service struct {
|
|||||||
ActualDigest string
|
ActualDigest string
|
||||||
Status string
|
Status string
|
||||||
Self bool
|
Self bool
|
||||||
|
Built bool // Compose service with a build: section; never updated
|
||||||
UpdateMode string
|
UpdateMode string
|
||||||
FirstSeenAt time.Time
|
FirstSeenAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
ALTER TABLE services ADD COLUMN config_files text NOT NULL DEFAULT '';
|
ALTER TABLE services ADD COLUMN config_files text NOT NULL DEFAULT '';
|
||||||
ALTER TABLE services ADD COLUMN desired_ref text NOT NULL DEFAULT '';
|
ALTER TABLE services ADD COLUMN desired_ref text NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE services ADD COLUMN built boolean NOT NULL DEFAULT false;
|
||||||
|
|
||||||
CREATE TABLE update_jobs (
|
CREATE TABLE update_jobs (
|
||||||
id bigserial PRIMARY KEY,
|
id bigserial PRIMARY KEY,
|
||||||
|
|||||||
@@ -40,13 +40,14 @@ func lockActiveAgent(ctx context.Context, tx pgx.Tx, agentID int64) error {
|
|||||||
const upsertServiceSQL = `
|
const upsertServiceSQL = `
|
||||||
INSERT INTO services (
|
INSERT INTO services (
|
||||||
kind, location_key, agent_id, compose_project, compose_service, working_dir, config_files,
|
kind, location_key, agent_id, compose_project, compose_service, working_dir, config_files,
|
||||||
swarm_cluster_id, swarm_service, container_name, image_repo, actual_ref, actual_digest,
|
built, swarm_cluster_id, swarm_service, container_name, image_repo, actual_ref, actual_digest,
|
||||||
status, self, first_seen_at, updated_at)
|
status, self, first_seen_at, updated_at)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $16)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $17)
|
||||||
ON CONFLICT (location_key) DO UPDATE SET
|
ON CONFLICT (location_key) DO UPDATE SET
|
||||||
agent_id = EXCLUDED.agent_id,
|
agent_id = EXCLUDED.agent_id,
|
||||||
working_dir = EXCLUDED.working_dir,
|
working_dir = EXCLUDED.working_dir,
|
||||||
config_files = EXCLUDED.config_files,
|
config_files = EXCLUDED.config_files,
|
||||||
|
built = EXCLUDED.built,
|
||||||
image_repo = EXCLUDED.image_repo,
|
image_repo = EXCLUDED.image_repo,
|
||||||
actual_ref = EXCLUDED.actual_ref,
|
actual_ref = EXCLUDED.actual_ref,
|
||||||
actual_digest = EXCLUDED.actual_digest,
|
actual_digest = EXCLUDED.actual_digest,
|
||||||
@@ -62,7 +63,7 @@ func upsertService(ctx context.Context, db execer, agentID int64, clusterID stri
|
|||||||
}
|
}
|
||||||
_, err := db.Exec(ctx, upsertServiceSQL,
|
_, err := db.Exec(ctx, upsertServiceSQL,
|
||||||
string(svc.Kind), model.LocationKey(agentID, clusterID, svc.Key()), agentID,
|
string(svc.Kind), model.LocationKey(agentID, clusterID, svc.Key()), agentID,
|
||||||
svc.ComposeProject, svc.ComposeService, svc.WorkingDir, svc.ConfigFiles,
|
svc.ComposeProject, svc.ComposeService, svc.WorkingDir, svc.ConfigFiles, svc.Built,
|
||||||
clusterCol, svc.SwarmService, svc.ContainerName,
|
clusterCol, svc.SwarmService, svc.ContainerName,
|
||||||
imageref.Parse(svc.Image).Repo, svc.Image, svc.Digest,
|
imageref.Parse(svc.Image).Repo, svc.Image, svc.Digest,
|
||||||
svc.Status, svc.Self, at)
|
svc.Status, svc.Self, at)
|
||||||
@@ -146,7 +147,7 @@ func (s *Store) RemoveService(ctx context.Context, agentID int64, clusterID, age
|
|||||||
}
|
}
|
||||||
|
|
||||||
const serviceColumns = `s.id, s.kind, s.location_key, s.agent_id, COALESCE(a.name, ''),
|
const serviceColumns = `s.id, s.kind, s.location_key, s.agent_id, COALESCE(a.name, ''),
|
||||||
s.compose_project, s.compose_service, s.working_dir, s.config_files, s.swarm_cluster_id,
|
s.compose_project, s.compose_service, s.working_dir, s.config_files, s.built, s.swarm_cluster_id,
|
||||||
s.swarm_service, s.container_name, s.image_repo, s.desired_ref, s.actual_ref, s.actual_digest,
|
s.swarm_service, s.container_name, s.image_repo, s.desired_ref, s.actual_ref, s.actual_digest,
|
||||||
s.status, s.self, s.update_mode, s.first_seen_at, s.updated_at, s.removed_at`
|
s.status, s.self, s.update_mode, s.first_seen_at, s.updated_at, s.removed_at`
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ func scanService(row pgx.Row) (model.Service, error) {
|
|||||||
var m model.Service
|
var m model.Service
|
||||||
var kind string
|
var kind string
|
||||||
err := row.Scan(&m.ID, &kind, &m.LocationKey, &m.AgentID, &m.AgentName,
|
err := row.Scan(&m.ID, &kind, &m.LocationKey, &m.AgentID, &m.AgentName,
|
||||||
&m.ComposeProject, &m.ComposeService, &m.WorkingDir, &m.ConfigFiles, &m.SwarmClusterID,
|
&m.ComposeProject, &m.ComposeService, &m.WorkingDir, &m.ConfigFiles, &m.Built, &m.SwarmClusterID,
|
||||||
&m.SwarmService, &m.ContainerName, &m.ImageRepo, &m.DesiredRef, &m.ActualRef, &m.ActualDigest,
|
&m.SwarmService, &m.ContainerName, &m.ImageRepo, &m.DesiredRef, &m.ActualRef, &m.ActualDigest,
|
||||||
&m.Status, &m.Self, &m.UpdateMode, &m.FirstSeenAt, &m.UpdatedAt, &m.RemovedAt)
|
&m.Status, &m.Self, &m.UpdateMode, &m.FirstSeenAt, &m.UpdatedAt, &m.RemovedAt)
|
||||||
m.Kind = protocol.Kind(kind)
|
m.Kind = protocol.Kind(kind)
|
||||||
|
|||||||
@@ -193,3 +193,23 @@ func TestApplySnapshot_StoresConfigFiles(t *testing.T) {
|
|||||||
require.Len(t, got, 1)
|
require.Len(t, got, 1)
|
||||||
assert.Equal(t, withFiles.ConfigFiles, got[0].ConfigFiles)
|
assert.Equal(t, withFiles.ConfigFiles, got[0].ConfigFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplySnapshot_StoresBuilt(t *testing.T) {
|
||||||
|
s := openTestStore(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
id := newAgent(t, s, "node-2")
|
||||||
|
built := slusk
|
||||||
|
built.Built = true
|
||||||
|
require.NoError(t, s.ApplySnapshot(ctx, id, worker, []protocol.Service{built}, t0))
|
||||||
|
|
||||||
|
got, err := s.ListServices(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, got, 1)
|
||||||
|
assert.True(t, got[0].Built)
|
||||||
|
|
||||||
|
// A later report without the flag clears it.
|
||||||
|
require.NoError(t, s.UpsertService(ctx, id, "", slusk, t0.Add(time.Minute)))
|
||||||
|
got, err = s.ListServices(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, got[0].Built)
|
||||||
|
}
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ func TestServicePage_ShowsUpdateButton(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServicePage_DisabledReason(t *testing.T) {
|
func TestServicePage_DisabledReason(t *testing.T) {
|
||||||
for _, reason := range []string{model.ReasonSelf, model.ReasonContainer, model.ReasonNoTag, model.ReasonRemoved} {
|
for _, reason := range []string{model.ReasonSelf, model.ReasonContainer, model.ReasonNoTag, model.ReasonRemoved, model.ReasonBuilt} {
|
||||||
t.Run(reason, func(t *testing.T) {
|
t.Run(reason, func(t *testing.T) {
|
||||||
srv, _, _, _, jobs := newTestEnv(t)
|
srv, _, _, _, jobs := newTestEnv(t)
|
||||||
jobs.updatable = &model.NotUpdatableError{Reason: reason}
|
jobs.updatable = &model.NotUpdatableError{Reason: reason}
|
||||||
|
|||||||
Reference in New Issue
Block a user