feat: self-update of master and agents (#15)
This commit is contained in:
@@ -92,6 +92,9 @@ type fakeHub struct {
|
||||
func (h *fakeHub) Route(_ model.Service, minProtocol int) (model.Route, error) {
|
||||
h.mins = append(h.mins, minProtocol)
|
||||
if h.agentProtocol != 0 && minProtocol > h.agentProtocol {
|
||||
if minProtocol >= protocol.MinSelfUpdateVersion {
|
||||
return model.Route{}, model.ErrAgentTooOldForSelfUpdate
|
||||
}
|
||||
if minProtocol >= protocol.MinPinnedRefVersion {
|
||||
return model.Route{}, model.ErrAgentTooOldForPinned
|
||||
}
|
||||
@@ -109,8 +112,6 @@ type fakePub struct{ topics []events.Topic }
|
||||
|
||||
func (p *fakePub) Publish(t events.Topic) { p.topics = append(p.topics, t) }
|
||||
|
||||
const selfImage = "gitea.shcizo.se/shcizo/package-updater:latest"
|
||||
|
||||
var (
|
||||
composeSvc = model.Service{ID: 1, Kind: protocol.KindCompose, ComposeProject: "slusk", ComposeService: "web",
|
||||
AgentID: ptr(int64(7)), ActualRef: "gitea.shcizo.se/shcizo/slusk:1.4.2@sha256:d1"}
|
||||
@@ -124,7 +125,7 @@ func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func newService(st *fakeStore, h *fakeHub) (*Service, *fakePub) {
|
||||
pub := &fakePub{}
|
||||
return New(st, h, pub, selfImage, slog.New(slog.NewTextHandler(io.Discard, nil))), pub
|
||||
return New(st, h, pub, slog.New(slog.NewTextHandler(io.Discard, nil))), pub
|
||||
}
|
||||
|
||||
func TestUpdatable(t *testing.T) {
|
||||
@@ -134,9 +135,10 @@ func TestUpdatable(t *testing.T) {
|
||||
reason string
|
||||
}{
|
||||
{"removed", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", RemovedAt: ptr(time.Now())}, model.ReasonRemoved},
|
||||
{"flagged self", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, model.ReasonSelf},
|
||||
{"agent's own Compose service", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, ""},
|
||||
{"agent's own Swarm service", model.Service{Kind: protocol.KindSwarm, ActualRef: "a:1", Self: true}, model.ReasonSelfSwarm},
|
||||
{"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"}, ""},
|
||||
{"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},
|
||||
{"digest without tag", model.Service{Kind: protocol.KindCompose, ActualRef: "redis@sha256:abc"}, model.ReasonNoTag},
|
||||
@@ -298,7 +300,8 @@ func TestStart_ExplicitRefBypassesOnlyTheNoTagRefusal(t *testing.T) {
|
||||
}{
|
||||
{"digest only", model.Service{Kind: protocol.KindCompose, ActualRef: "redis@sha256:abc"}, ""},
|
||||
{"removed", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", RemovedAt: ptr(time.Now())}, model.ReasonRemoved},
|
||||
{"self", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, model.ReasonSelf},
|
||||
{"own Swarm service", model.Service{Kind: protocol.KindSwarm, ActualRef: "a:1", Self: true}, model.ReasonSelfSwarm},
|
||||
{"own Compose service", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, ""},
|
||||
{"built", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Built: true}, model.ReasonBuilt},
|
||||
{"container", model.Service{Kind: protocol.KindContainer, ActualRef: "redis:7"}, model.ReasonContainer},
|
||||
}
|
||||
@@ -342,8 +345,9 @@ func TestAutoAllowed(t *testing.T) {
|
||||
reason string
|
||||
}{
|
||||
{"removed", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", RemovedAt: ptr(time.Now())}, model.ReasonRemoved},
|
||||
{"self", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, model.ReasonSelf},
|
||||
{"agent image repo", model.Service{Kind: protocol.KindCompose, ActualRef: selfImage}, model.ReasonSelf},
|
||||
{"own Swarm service", model.Service{Kind: protocol.KindSwarm, ActualRef: "a:1", Self: true}, model.ReasonSelfSwarm},
|
||||
{"own Compose service", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Self: true}, ""},
|
||||
{"agent image repo", model.Service{Kind: protocol.KindCompose, ActualRef: "gitea.shcizo.se/shcizo/package-updater:latest"}, ""},
|
||||
{"built", model.Service{Kind: protocol.KindCompose, ActualRef: "a:1", Built: true}, model.ReasonBuilt},
|
||||
{"container", model.Service{Kind: protocol.KindContainer, ActualRef: "redis:7"}, model.ReasonContainer},
|
||||
{"digest without tag is allowed", model.Service{Kind: protocol.KindCompose, ActualRef: "redis@sha256:abc"}, ""},
|
||||
@@ -396,6 +400,47 @@ func TestStart_RoutesWithTheMinimumProtocolOfTheRef(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStart_SelfServiceNeedsASelfUpdatingAgent(t *testing.T) {
|
||||
self := composeSvc
|
||||
self.Self = true
|
||||
ok := model.Route{AgentID: 7, ConnID: 70}
|
||||
|
||||
t.Run("routes with the self-update protocol and marks the job", func(t *testing.T) {
|
||||
st := &fakeStore{services: map[int64]model.Service{1: self}, jobs: map[int64]model.Job{}}
|
||||
h := &fakeHub{route: ok}
|
||||
s, _ := newService(st, h)
|
||||
|
||||
_, err := s.Start(context.Background(), self, StartRequest{Trigger: model.TriggerManual, Ref: "x/y:2@sha256:a"})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []int{protocol.MinSelfUpdateVersion}, h.mins, "also above the pinned-ref minimum")
|
||||
require.Len(t, h.starts, 1)
|
||||
assert.True(t, h.starts[0].start.Self)
|
||||
})
|
||||
t.Run("a service that is not the agent's own is not marked", func(t *testing.T) {
|
||||
st := &fakeStore{services: map[int64]model.Service{1: composeSvc}, jobs: map[int64]model.Job{}}
|
||||
h := &fakeHub{route: ok}
|
||||
s, _ := newService(st, h)
|
||||
|
||||
_, err := s.Start(context.Background(), composeSvc, StartRequest{Trigger: model.TriggerManual})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.False(t, h.starts[0].start.Self)
|
||||
})
|
||||
t.Run("an older agent fails the job with a clear error", func(t *testing.T) {
|
||||
st := &fakeStore{services: map[int64]model.Service{1: self}, jobs: map[int64]model.Job{}}
|
||||
h := &fakeHub{route: ok, agentProtocol: 3}
|
||||
s, _ := newService(st, h)
|
||||
|
||||
job, err := s.Start(context.Background(), self, StartRequest{Trigger: model.TriggerManual})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.JobFailed, job.Status)
|
||||
assert.Equal(t, "agent too old to update itself", job.Error)
|
||||
assert.Empty(t, h.starts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTriggerVersion_Ref(t *testing.T) {
|
||||
const repo = "gitea.shcizo.se/shcizo/slusk"
|
||||
noTag := model.Service{ID: 4, Kind: protocol.KindCompose, ComposeProject: "n", ComposeService: "n",
|
||||
|
||||
Reference in New Issue
Block a user