feat: master/agent phase 4 — registry polling and version history (#9) (#23)
Docker Build / docker (push) Successful in 47s
Docker Build / docker (push) Successful in 47s
This commit was merged in pull request #23.
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
//go:build integration
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/shcizo/package-updater/internal/master/model"
|
||||
"github.com/shcizo/package-updater/internal/protocol"
|
||||
)
|
||||
|
||||
const sluskRepo = "gitea.shcizo.se/shcizo/slusk"
|
||||
|
||||
// Digests of the fixture services; only well-formed ones are recorded as versions.
|
||||
var (
|
||||
digD1 = "sha256:" + strings.Repeat("d", 63) + "1"
|
||||
digGitea = "sha256:" + strings.Repeat("a", 63) + "1"
|
||||
digPg = "sha256:" + strings.Repeat("b", 63) + "1"
|
||||
)
|
||||
|
||||
func versionsOf(t *testing.T, s *Store, repo string) []model.ImageVersion {
|
||||
t.Helper()
|
||||
got, err := s.RecentVersions(context.Background(), repo, 100)
|
||||
require.NoError(t, err)
|
||||
return got
|
||||
}
|
||||
|
||||
func TestSetAvailable_RecordsVersion(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
_, svc := jobFixture(t, s, "a")
|
||||
|
||||
require.NoError(t, s.SetAvailable(ctx, svc, model.Available{Ref: sluskRepo + ":1.5.0", Digest: "sha256:n1", Source: model.SourcePoll, At: t0}))
|
||||
|
||||
got, err := s.GetService(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, sluskRepo+":1.5.0", got.AvailableRef)
|
||||
assert.Equal(t, "sha256:n1", got.AvailableDigest)
|
||||
assert.Equal(t, model.SourcePoll, got.AvailableSource)
|
||||
vs := versionsOf(t, s, sluskRepo)
|
||||
var found model.ImageVersion
|
||||
for _, v := range vs {
|
||||
if v.Digest == "sha256:n1" {
|
||||
found = v
|
||||
}
|
||||
}
|
||||
require.NotZero(t, found.ID)
|
||||
assert.Equal(t, "1.5.0", found.Tag)
|
||||
assert.Equal(t, model.SourcePoll, found.Source)
|
||||
|
||||
// Seeing the same version again keeps the first sighting.
|
||||
require.NoError(t, s.SetAvailable(ctx, svc, model.Available{Ref: sluskRepo + ":1.5.0", Digest: "sha256:n1", Source: model.SourceWebhook, At: t0.Add(time.Hour)}))
|
||||
again, err := s.GetImageVersion(ctx, found.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.SourcePoll, again.Source)
|
||||
assert.True(t, t0.Equal(again.FirstSeenAt))
|
||||
assert.Len(t, versionsOf(t, s, sluskRepo), len(vs))
|
||||
}
|
||||
|
||||
func TestSnapshotRecordsRunningVersions(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
id := newAgent(t, s, "node-2")
|
||||
tagless := protocol.Service{Kind: protocol.KindSwarm, SwarmService: "g_g", Image: "gitea/gitea@" + digGitea, Digest: digGitea, Status: "running"}
|
||||
bare := protocol.Service{Kind: protocol.KindContainer, ContainerName: "pg", Image: "postgres", Digest: digPg, Status: "running"}
|
||||
nodigest := protocol.Service{Kind: protocol.KindContainer, ContainerName: "nd", Image: "nodigest:1", Status: "running"}
|
||||
hello := protocol.Hello{Hostname: "node-2", SwarmManager: true, SwarmClusterID: "c1"}
|
||||
svcs := []protocol.Service{slusk, tagless, bare, nodigest}
|
||||
|
||||
require.NoError(t, s.ApplySnapshot(ctx, id, hello, svcs, t0))
|
||||
require.NoError(t, s.ApplySnapshot(ctx, id, hello, svcs, t0.Add(time.Hour)))
|
||||
|
||||
got := versionsOf(t, s, sluskRepo)
|
||||
require.Len(t, got, 1)
|
||||
assert.Equal(t, "1.4.2", got[0].Tag)
|
||||
assert.Equal(t, digD1, got[0].Digest)
|
||||
assert.Equal(t, model.SourceRunning, got[0].Source)
|
||||
assert.True(t, t0.Equal(got[0].FirstSeenAt), "re-snapshot does not bump first_seen_at")
|
||||
tl := versionsOf(t, s, "gitea/gitea")
|
||||
require.Len(t, tl, 1)
|
||||
assert.Equal(t, "", tl[0].Tag)
|
||||
assert.Equal(t, digGitea, tl[0].Digest)
|
||||
b := versionsOf(t, s, "postgres")
|
||||
require.Len(t, b, 1)
|
||||
assert.Equal(t, "latest", b[0].Tag, "a bare image runs latest, like the followed ref")
|
||||
assert.Empty(t, versionsOf(t, s, "nodigest"))
|
||||
}
|
||||
|
||||
func TestSnapshotRecordsOnlyWellFormedDigests(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
id := newAgent(t, s, "node-2")
|
||||
bad := protocol.Service{Kind: protocol.KindContainer, ContainerName: "bad", Image: "bad:1", Digest: "sha256:short", Status: "running"}
|
||||
upper := protocol.Service{Kind: protocol.KindContainer, ContainerName: "up", Image: "up:1", Digest: "sha256:" + strings.Repeat("A", 64), Status: "running"}
|
||||
|
||||
require.NoError(t, s.ApplySnapshot(context.Background(), id, protocol.Hello{Hostname: "node-2"}, []protocol.Service{bad, upper}, t0))
|
||||
|
||||
assert.Empty(t, versionsOf(t, s, "bad"))
|
||||
assert.Empty(t, versionsOf(t, s, "up"))
|
||||
}
|
||||
|
||||
func TestRecentVersions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("order, limit and repo filter", func(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
_, svc := jobFixture(t, s, "a")
|
||||
require.NoError(t, s.SetAvailable(ctx, svc, model.Available{Ref: "other/repo:x", Digest: "sha256:ox", Source: model.SourcePoll, At: t0}))
|
||||
// 12 versions of the service's repo, one per minute.
|
||||
for i := 0; i < 12; i++ {
|
||||
tag := "t" + string(rune('a'+i))
|
||||
require.NoError(t, s.SetAvailable(ctx, svc, model.Available{Ref: sluskRepo + ":" + tag, Digest: "sha256:" + tag, Source: model.SourcePoll, At: t0.Add(time.Duration(i+1) * time.Minute)}))
|
||||
}
|
||||
|
||||
got, err := s.RecentVersions(ctx, sluskRepo, 10)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 10)
|
||||
assert.Equal(t, "tl", got[0].Tag, "newest first")
|
||||
assert.Equal(t, "tc", got[9].Tag)
|
||||
for _, v := range got {
|
||||
assert.Equal(t, sluskRepo, v.Repo)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("digest-less row is hidden once its tag has a digest", func(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
_, svc := jobFixture(t, s, "a")
|
||||
add := func(tag, digest string, at time.Time) {
|
||||
require.NoError(t, s.SetAvailable(ctx, svc, model.Available{Ref: sluskRepo + ":" + tag, Digest: digest, Source: model.SourceWebhook, At: at}))
|
||||
}
|
||||
add("main", "", t0.Add(time.Minute))
|
||||
add("solo", "", t0.Add(2*time.Minute))
|
||||
require.Len(t, versionsOf(t, s, sluskRepo), 3, "running 1.4.2 plus two digest-less rows")
|
||||
|
||||
add("main", "sha256:m1", t0.Add(3*time.Minute))
|
||||
|
||||
var refs []string
|
||||
for _, v := range versionsOf(t, s, sluskRepo) {
|
||||
refs = append(refs, v.Ref())
|
||||
}
|
||||
assert.Equal(t, []string{sluskRepo + ":main@sha256:m1", sluskRepo + ":solo", sluskRepo + ":1.4.2@" + digD1}, refs)
|
||||
})
|
||||
|
||||
t.Run("digest-less row newer than the digest row of its tag is shown", func(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
_, svc := jobFixture(t, s, "a")
|
||||
add := func(tag, digest string, at time.Time) {
|
||||
require.NoError(t, s.SetAvailable(ctx, svc, model.Available{Ref: sluskRepo + ":" + tag, Digest: digest, Source: model.SourceWebhook, At: at}))
|
||||
}
|
||||
add("main", "sha256:m1", t0.Add(time.Minute))
|
||||
add("main", "", t0.Add(2*time.Minute))
|
||||
|
||||
var refs []string
|
||||
for _, v := range versionsOf(t, s, sluskRepo) {
|
||||
refs = append(refs, v.Ref())
|
||||
}
|
||||
assert.Equal(t, []string{sluskRepo + ":main", sluskRepo + ":main@sha256:m1", sluskRepo + ":1.4.2@" + digD1}, refs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetImageVersion_NotFound(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
|
||||
_, err := s.GetImageVersion(context.Background(), 99999)
|
||||
|
||||
assert.ErrorIs(t, err, model.ErrNotFound)
|
||||
}
|
||||
|
||||
func TestSetPollError(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
agent, svc := jobFixture(t, s, "a")
|
||||
require.NoError(t, s.SetAvailable(ctx, svc, model.Available{Ref: sluskRepo + ":1.5.0", Digest: "sha256:n1", Source: model.SourcePoll, At: t0}))
|
||||
|
||||
require.NoError(t, s.SetPollError(ctx, []int64{svc}, "boom"))
|
||||
require.NoError(t, s.SetPollError(ctx, nil, "ignored"))
|
||||
got, err := s.GetService(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "boom", got.LastPollError)
|
||||
|
||||
require.NoError(t, s.UpsertService(ctx, agent, "", slusk, t0.Add(time.Hour)))
|
||||
got, err = s.GetService(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "boom", got.LastPollError, "inventory writes do not touch the poll error")
|
||||
assert.Equal(t, "sha256:n1", got.AvailableDigest, "nor the suggestion")
|
||||
|
||||
require.NoError(t, s.SetPollError(ctx, []int64{svc}, ""))
|
||||
got, err = s.GetService(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, got.LastPollError)
|
||||
}
|
||||
|
||||
func TestSetLastTagDigest(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
agent, svc := jobFixture(t, s, "a")
|
||||
got, err := s.GetService(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, got.LastTagDigest)
|
||||
|
||||
require.NoError(t, s.SetLastTagDigest(ctx, svc, "sha256:n1"))
|
||||
require.NoError(t, s.UpsertService(ctx, agent, "", slusk, t0.Add(time.Hour)))
|
||||
|
||||
got, err = s.GetService(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "sha256:n1", got.LastTagDigest, "inventory writes do not touch it")
|
||||
list, err := s.ListServices(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
assert.Equal(t, "sha256:n1", list[0].LastTagDigest)
|
||||
}
|
||||
Reference in New Issue
Block a user