Files
package-updater/internal/agent/updater/swarm_test.go
T

159 lines
5.9 KiB
Go

package updater
import (
"bytes"
"context"
"errors"
"testing"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/shcizo/package-updater/internal/protocol"
)
type fakeSwarm struct {
info system.Info
svc swarm.Service
inspectErr error
updateErr error
warnings []string
inspectCalls int
updateCalls int
updID string
updVersion swarm.Version
updSpec swarm.ServiceSpec
updOpts swarm.ServiceUpdateOptions
}
func (f *fakeSwarm) Info(context.Context) (system.Info, error) { return f.info, nil }
func (f *fakeSwarm) ServiceInspectWithRaw(context.Context, string, swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
f.inspectCalls++
return f.svc, nil, f.inspectErr
}
func (f *fakeSwarm) ServiceUpdate(_ context.Context, id string, v swarm.Version, spec swarm.ServiceSpec, opts swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
f.updateCalls++
f.updID, f.updVersion, f.updSpec, f.updOpts = id, v, spec, opts
return swarm.ServiceUpdateResponse{Warnings: f.warnings}, f.updateErr
}
type fakeDist struct {
digest string
err error
refs []string
auths []string
}
func (f *fakeDist) DistributionInspect(_ context.Context, ref, auth string) (registry.DistributionInspect, error) {
f.refs = append(f.refs, ref)
f.auths = append(f.auths, auth)
var d registry.DistributionInspect
d.Descriptor.Digest = digest.Digest(f.digest)
return d, f.err
}
func managerOf(cluster string) system.Info {
var info system.Info
info.Swarm.ControlAvailable = true
info.Swarm.Cluster = &swarm.ClusterInfo{ID: cluster}
return info
}
func swarmSvc(name string) swarm.Service {
return swarm.Service{
ID: "svc-id",
Meta: swarm.Meta{Version: swarm.Version{Index: 7}},
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: name},
TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: "gitea/gitea:1.26"}},
},
}
}
func TestSwarmUsesPinnedRef(t *testing.T) {
const ref = "gitea/gitea:1.26@sha256:abababababababababababababababababababababababababababababababab"
job := Job{JobStart: protocol.JobStart{Kind: protocol.KindSwarm, SwarmClusterID: "c1", SwarmService: "gitea_gitea", Ref: ref}}
fake := &fakeSwarm{info: managerOf("c1"), svc: swarmSvc("gitea_gitea")}
dist := &fakeDist{digest: "sha256:other"}
var out bytes.Buffer
got, err := NewSwarmExecutor(fake, NewResolver(dist, nil), nil).Execute(context.Background(), job, &out)
require.NoError(t, err)
assert.Equal(t, ref, got)
assert.Equal(t, ref, fake.updSpec.TaskTemplate.ContainerSpec.Image)
assert.Empty(t, dist.refs, "no registry lookup")
assert.Contains(t, out.String(), "pinned "+ref)
}
func TestSwarmExecutor(t *testing.T) {
job := Job{JobStart: protocol.JobStart{Kind: protocol.KindSwarm, SwarmClusterID: "c1", SwarmService: "gitea_gitea", Ref: "gitea/gitea:1.27"}}
workerInfo := managerOf("c1")
workerInfo.Swarm.ControlAvailable = false
noContainer := swarmSvc("gitea_gitea")
noContainer.Spec.TaskTemplate.ContainerSpec = nil
cases := []struct {
name string
fake *fakeSwarm
auth *RegistryAuth
wantErr string
wantUpdate bool
wantEncAuth bool
}{
{name: "happy path, no auth", fake: &fakeSwarm{info: managerOf("c1"), svc: swarmSvc("gitea_gitea")}, wantUpdate: true},
{name: "happy path, auth known", fake: &fakeSwarm{info: managerOf("c1"), svc: swarmSvc("gitea_gitea"), warnings: []string{"w1"}},
auth: &RegistryAuth{byHost: map[string]string{"docker.io": "enc"}}, wantUpdate: true, wantEncAuth: true},
{name: "cluster mismatch", fake: &fakeSwarm{info: managerOf("other"), svc: swarmSvc("gitea_gitea")},
wantErr: "refused: job is for swarm cluster c1, this daemon is in other"},
{name: "worker node", fake: &fakeSwarm{info: workerInfo, svc: swarmSvc("gitea_gitea")}, wantErr: "not a swarm manager"},
{name: "other service returned", fake: &fakeSwarm{info: managerOf("c1"), svc: swarmSvc("gitea_other")}, wantErr: "daemon returned service"},
{name: "no container spec", fake: &fakeSwarm{info: managerOf("c1"), svc: noContainer}, wantErr: "no container spec"},
{name: "inspect fails", fake: &fakeSwarm{info: managerOf("c1"), inspectErr: errors.New("gone")}, wantErr: "gone"},
{name: "update fails", fake: &fakeSwarm{info: managerOf("c1"), svc: swarmSvc("gitea_gitea"), updateErr: errors.New("rejected")},
wantErr: "rejected", wantUpdate: true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
dist := &fakeDist{digest: "sha256:" + string(bytes.Repeat([]byte("a"), 64))}
e := NewSwarmExecutor(c.fake, NewResolver(dist, c.auth), c.auth)
var out bytes.Buffer
ref, err := e.Execute(context.Background(), job, &out)
if c.wantErr != "" {
assert.ErrorContains(t, err, c.wantErr)
} else {
require.NoError(t, err)
assert.Equal(t, "gitea/gitea:1.27@"+dist.digest, ref)
assert.Contains(t, out.String(), "resolved "+ref)
}
if c.wantErr == "refused: job is for swarm cluster c1, this daemon is in other" || c.wantErr == "not a swarm manager" {
assert.Zero(t, c.fake.inspectCalls)
}
if !c.wantUpdate {
assert.Zero(t, c.fake.updateCalls)
return
}
assert.Equal(t, 1, c.fake.updateCalls)
assert.Equal(t, "svc-id", c.fake.updID)
assert.Equal(t, uint64(7), c.fake.updVersion.Index)
assert.Equal(t, "gitea/gitea:1.27@"+dist.digest, c.fake.updSpec.TaskTemplate.ContainerSpec.Image)
assert.True(t, c.fake.updOpts.QueryRegistry)
if c.wantEncAuth {
assert.Equal(t, "enc", c.fake.updOpts.EncodedRegistryAuth)
assert.Empty(t, c.fake.updOpts.RegistryAuthFrom)
assert.Contains(t, out.String(), "warning: w1")
} else {
assert.Empty(t, c.fake.updOpts.EncodedRegistryAuth)
assert.Equal(t, swarm.RegistryAuthFromSpec, c.fake.updOpts.RegistryAuthFrom)
}
})
}
}