34865c9fd0
Adds the Swarm-mode counterpart to ComposeExecutor: updates a Swarm service's image directly through the Docker SDK (ServiceInspectWithRaw + ServiceUpdate with QueryRegistry=true so floating tags resolve to a fresh digest), gated by the same Refused guard used in Compose mode. Satisfies the existing Executor interface unchanged, so Queue needs no changes.
53 lines
1.9 KiB
Go
53 lines
1.9 KiB
Go
package updater
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/shcizo/package-updater/internal/discovery"
|
|
)
|
|
|
|
// SwarmDockerClient is the subset of the Docker SDK SwarmExecutor depends
|
|
// on. Defined as an interface so tests can supply a fake.
|
|
type SwarmDockerClient interface {
|
|
ServiceInspectWithRaw(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
|
|
ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, opts swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
|
}
|
|
|
|
// SwarmExecutor updates a Swarm service's image via the Docker API,
|
|
// equivalent to `docker service update --image`.
|
|
type SwarmExecutor struct {
|
|
cli SwarmDockerClient
|
|
}
|
|
|
|
// NewSwarmExecutor returns an executor that drives Swarm service updates
|
|
// through the Docker API.
|
|
func NewSwarmExecutor(cli SwarmDockerClient) *SwarmExecutor {
|
|
return &SwarmExecutor{cli: cli}
|
|
}
|
|
|
|
// Execute inspects the service to get its current spec and version (required
|
|
// by the Docker API as an optimistic-concurrency token), sets the new image
|
|
// on the container spec, and calls ServiceUpdate. QueryRegistry is set so a
|
|
// floating tag (e.g. ":latest") resolves to a fresh digest and actually
|
|
// triggers a rolling update instead of being treated as unchanged.
|
|
func (e *SwarmExecutor) Execute(ctx context.Context, job discovery.Job) error {
|
|
if job.Refused {
|
|
return fmt.Errorf("refused: %s", job.RefusedReason)
|
|
}
|
|
|
|
svc, _, err := e.cli.ServiceInspectWithRaw(ctx, job.ServiceID, swarm.ServiceInspectOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("inspect service %s: %w", job.ServiceID, err)
|
|
}
|
|
|
|
spec := svc.Spec
|
|
spec.TaskTemplate.ContainerSpec.Image = job.Image
|
|
|
|
if _, err := e.cli.ServiceUpdate(ctx, job.ServiceID, svc.Version, spec, swarm.ServiceUpdateOptions{QueryRegistry: true}); err != nil {
|
|
return fmt.Errorf("update service %s: %w", job.ServiceID, err)
|
|
}
|
|
return nil
|
|
}
|