Docker Build / docker (push) Successful in 42s
## Summary
- Drift detection: `model.Service.Drifted()` compares desired and running version by digest (tag when the digest is unknown). `services.awaiting_inventory` hides drift between a job result and the next inventory write.
- Poll and webhook jobs are refused for a drifted service; `POST /services/{id}/restore` (manual job to `desired_ref`) and `POST /services/{id}/accept` (running version becomes desired, guarded by `actual` = `Service.RunningRef`).
- Move suggestions (`store.MoveCandidates`, Compose only) and `POST /services/{id}/merge`: the old row keeps its id, mode, `desired_ref` and history and takes the new location; the duplicate is deleted.
- Backend only. The buttons and badges come with #28, on top of the Ledger UI (#18).
Closes #10
## Test plan
- [x] `gofmt -l .`, `go vet ./...`, `go test ./...`
- [x] `go test -race ./internal/agent/... ./internal/master/... ./internal/e2e/`
- [x] `go test -tags integration ./internal/master/store/` against postgres:17-alpine
- [ ] On the server: migration 0005 applies; no drift shown after a normal update
- [ ] On the server: manual `docker compose up` with another tag gives drift; webhook for an auto service is refused
- [ ] On the server: restore and accept via POST; merge after moving a stack
Reviewed-on: #30
47 lines
1.3 KiB
Go
47 lines
1.3 KiB
Go
package imageref
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestParse(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want Ref
|
|
}{
|
|
{"postgres", Ref{Repo: "postgres"}},
|
|
{"postgres:17.10", Ref{Repo: "postgres", Tag: "17.10"}},
|
|
{"gitea/gitea:1.27.3@sha256:87a6", Ref{Repo: "gitea/gitea", Tag: "1.27.3", Digest: "sha256:87a6"}},
|
|
{"lscr.io/linuxserver/jellyfin@sha256:0f42", Ref{Repo: "lscr.io/linuxserver/jellyfin", Digest: "sha256:0f42"}},
|
|
{"localhost:5000/foo", Ref{Repo: "localhost:5000/foo"}},
|
|
{"localhost:5000/foo:v1", Ref{Repo: "localhost:5000/foo", Tag: "v1"}},
|
|
{"localhost:5000/foo:v1@sha256:ab", Ref{Repo: "localhost:5000/foo", Tag: "v1", Digest: "sha256:ab"}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.in, func(t *testing.T) {
|
|
got := Parse(c.in)
|
|
assert.Equal(t, c.want, got)
|
|
assert.Equal(t, c.in, got.String(), "String must round-trip")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsDigest(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want bool
|
|
}{
|
|
{"sha256:" + strings.Repeat("a", 64), true},
|
|
{"sha256:" + strings.Repeat("a", 63), false},
|
|
{"sha256:" + strings.Repeat("A", 64), false},
|
|
{"sha512:" + strings.Repeat("a", 64), false},
|
|
{"", false},
|
|
}
|
|
for _, c := range cases {
|
|
assert.Equal(t, c.want, IsDigest(c.in), c.in)
|
|
}
|
|
}
|