134 lines
4.9 KiB
Go
134 lines
4.9 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/shcizo/package-updater/internal/master/jobs"
|
|
"github.com/shcizo/package-updater/internal/master/model"
|
|
"github.com/shcizo/package-updater/internal/master/poller"
|
|
"github.com/shcizo/package-updater/internal/protocol"
|
|
)
|
|
|
|
// jobCount is the number of jobs created so far.
|
|
func (m *memStore) jobCount() int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return len(m.jobs)
|
|
}
|
|
|
|
// fixedRegistry answers every lookup from a fixed table.
|
|
type fixedRegistry struct {
|
|
digests map[string]string
|
|
calls int
|
|
}
|
|
|
|
func (f *fixedRegistry) Digest(_ context.Context, ref string) (string, error) {
|
|
f.calls++
|
|
return f.digests[ref], nil
|
|
}
|
|
|
|
func TestPickPinnedVersion(t *testing.T) {
|
|
env := newJobEnv(t)
|
|
env.store.seedVersion(model.ImageVersion{ID: 5, Repo: slusk, Tag: "1.3.0", Digest: "sha256:old130", Source: model.SourceRunning})
|
|
|
|
job, err := env.jobs.TriggerVersion(context.Background(), 1, 5, "alice")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, slusk+":1.3.0@sha256:old130", job.ToRef)
|
|
select {
|
|
case ref := <-env.exec.started:
|
|
assert.Equal(t, slusk+":1.3.0@sha256:old130", ref, "the agent receives exactly the picked version")
|
|
case <-time.After(waitFor):
|
|
t.Fatal("the agent never received the job")
|
|
}
|
|
close(env.exec.release)
|
|
done := env.waitStatus(t, job.ID, model.JobSucceeded)
|
|
assert.Equal(t, model.TriggerManual, done.Trigger)
|
|
assert.Equal(t, "alice", done.RequestedBy)
|
|
}
|
|
|
|
func TestPickVersionOfAnotherRepoIsRefused(t *testing.T) {
|
|
env := newJobEnv(t)
|
|
env.store.seedVersion(model.ImageVersion{ID: 6, Repo: "evil/other", Tag: "1", Digest: "sha256:x", Source: model.SourcePoll})
|
|
|
|
_, err := env.jobs.TriggerVersion(context.Background(), 1, 6, "alice")
|
|
|
|
assert.ErrorIs(t, err, model.ErrNotFound)
|
|
assert.Zero(t, env.store.jobCount(), "no job was created")
|
|
}
|
|
|
|
func TestPollStartsAutoJob(t *testing.T) {
|
|
env := newJobEnv(t)
|
|
env.store.seedService(model.Service{ID: 1, Kind: protocol.KindCompose, ComposeProject: "slusk", ComposeService: "web",
|
|
AgentID: ptr(agentID), ImageRepo: slusk, ActualRef: slusk + ":1.4.2@sha256:old", ActualDigest: "sha256:old",
|
|
UpdateMode: model.ModeAuto})
|
|
reg := &fixedRegistry{digests: map[string]string{slusk + ":1.4.2": "sha256:new"}}
|
|
p := poller.New(env.store, env.jobs, reg, env.m.broker, poller.Config{Workers: 1}, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
|
|
p.Cycle(context.Background())
|
|
|
|
select {
|
|
case ref := <-env.exec.started:
|
|
assert.Equal(t, slusk+":1.4.2", ref, "the agent gets repo:tag and resolves the digest itself")
|
|
case <-time.After(waitFor):
|
|
t.Fatal("the agent never received the job")
|
|
}
|
|
require.Equal(t, 1, env.store.jobCount())
|
|
job := env.job(t, 1)
|
|
assert.Equal(t, model.TriggerPoll, job.Trigger)
|
|
assert.Equal(t, slusk+":1.4.2", job.ToRef)
|
|
svc, err := env.store.GetService(context.Background(), 1)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, slusk+":1.4.2", svc.AvailableRef)
|
|
assert.Equal(t, "sha256:new", svc.AvailableDigest)
|
|
assert.Equal(t, model.SourcePoll, svc.AvailableSource)
|
|
assert.Equal(t, "sha256:new", svc.LastTagDigest)
|
|
|
|
p.Cycle(context.Background())
|
|
|
|
assert.Equal(t, 2, reg.calls, "the registry is asked again")
|
|
assert.Equal(t, 1, env.store.jobCount(), "the same digest does not start another job")
|
|
close(env.exec.release)
|
|
env.waitStatus(t, job.ID, model.JobSucceeded)
|
|
}
|
|
|
|
// An update started by the webhook, then a rollback by hand: polling must not
|
|
// redo the update while the registry still shows the digest that was deployed.
|
|
func TestPollKeepsARollbackAfterAWebhookUpdate(t *testing.T) {
|
|
env := newJobEnv(t)
|
|
ctx := context.Background()
|
|
close(env.exec.release)
|
|
env.store.seedService(model.Service{ID: 1, Kind: protocol.KindCompose, ComposeProject: "slusk", ComposeService: "web",
|
|
AgentID: ptr(agentID), ImageRepo: slusk, ActualRef: slusk + ":main@sha256:old", ActualDigest: "sha256:old",
|
|
UpdateMode: model.ModeAuto})
|
|
env.store.seedVersion(model.ImageVersion{ID: 5, Repo: slusk, Tag: "main", Digest: "sha256:old", Source: model.SourceRunning})
|
|
svc, err := env.store.GetService(ctx, 1)
|
|
require.NoError(t, err)
|
|
|
|
hook, err := env.jobs.Start(ctx, svc, jobs.StartRequest{Trigger: model.TriggerWebhook, Ref: slusk + ":main"})
|
|
require.NoError(t, err)
|
|
env.waitStatus(t, hook.ID, model.JobSucceeded)
|
|
svc, err = env.store.GetService(ctx, 1)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, e2eDigest, svc.LastTagDigest, "the deployed digest of the tag is remembered")
|
|
|
|
pick, err := env.jobs.TriggerVersion(ctx, 1, 5, "alice")
|
|
require.NoError(t, err)
|
|
env.waitStatus(t, pick.ID, model.JobSucceeded)
|
|
jobsBefore := env.store.jobCount()
|
|
|
|
reg := &fixedRegistry{digests: map[string]string{slusk + ":main": e2eDigest}}
|
|
p := poller.New(env.store, env.jobs, reg, env.m.broker, poller.Config{Workers: 1}, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
p.Cycle(ctx)
|
|
|
|
assert.Equal(t, 1, reg.calls)
|
|
assert.Equal(t, jobsBefore, env.store.jobCount(), "the rollback stays")
|
|
}
|