a8524e5ff4
Verified the Docker SDK v28.5.2+incompatible ServiceList signature via go doc before implementing: the options type is swarm.ServiceListOptions, not types.ServiceListOptions as the brief assumed. Everything else (Service.ID, Service.Spec via embedded Annotations for Name/Labels, TaskTemplate.ContainerSpec.Image) matched the brief exactly. Reuses discovery.ImagesMatch and discovery.HasOptIn rather than duplicating matching/opt-in logic. Opt-in label is read from the service spec's own labels since Swarm services have no local compose file to anchor a STACKS_ROOT path check against (unlike Compose mode).
61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package discovery
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/docker/docker/api/types/swarm"
|
|
)
|
|
|
|
// SwarmDockerClient is the subset of the Docker SDK SwarmDiscovery depends
|
|
// on. Defined as an interface so tests can supply a fake.
|
|
type SwarmDockerClient interface {
|
|
ServiceList(ctx context.Context, opts swarm.ServiceListOptions) ([]swarm.Service, error)
|
|
}
|
|
|
|
// SwarmDiscovery orchestrates "given an image, which Swarm services should
|
|
// we update?". Unlike Discovery, it has no filesystem path to check — the
|
|
// opt-in label on the service itself is the only gate, since Swarm services
|
|
// have no local compose file to anchor a STACKS_ROOT check against.
|
|
type SwarmDiscovery struct {
|
|
cli SwarmDockerClient
|
|
optInLabel string
|
|
}
|
|
|
|
// NewSwarm returns a SwarmDiscovery bound to the given Docker client and
|
|
// opt-in label.
|
|
func NewSwarm(cli SwarmDockerClient, optInLabel string) *SwarmDiscovery {
|
|
return &SwarmDiscovery{cli: cli, optInLabel: optInLabel}
|
|
}
|
|
|
|
// FindJobs lists Swarm services, filters by image match + opt-in label, and
|
|
// returns one Job per matching service. Signature matches Discovery.FindJobs
|
|
// so both satisfy api.Finder.
|
|
func (d *SwarmDiscovery) FindJobs(ctx context.Context, image string) ([]Job, error) {
|
|
services, err := d.cli.ServiceList(ctx, swarm.ServiceListOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("docker service list: %w", err)
|
|
}
|
|
|
|
var jobs []Job
|
|
for _, svc := range services {
|
|
if svc.Spec.TaskTemplate.ContainerSpec == nil {
|
|
continue
|
|
}
|
|
if !ImagesMatch(image, svc.Spec.TaskTemplate.ContainerSpec.Image) {
|
|
continue
|
|
}
|
|
if !HasOptIn(svc.Spec.Labels, d.optInLabel) {
|
|
continue
|
|
}
|
|
|
|
jobs = append(jobs, Job{
|
|
Service: svc.Spec.Name,
|
|
ServiceID: svc.ID,
|
|
Image: image,
|
|
})
|
|
}
|
|
|
|
return jobs, nil
|
|
}
|