159 lines
5.9 KiB
Go
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 := 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 := 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)
|
|
}
|
|
})
|
|
}
|
|
}
|