diff --git a/cmd/uncloud/service/run.go b/cmd/uncloud/service/run.go index f8fc905d..6fb49ea2 100644 --- a/cmd/uncloud/service/run.go +++ b/cmd/uncloud/service/run.go @@ -21,6 +21,7 @@ type runOptions struct { mode string name string publish []string + pull string replicas uint volumes []string @@ -50,8 +51,8 @@ func NewRunCommand() *cobra.Command { cmd.Flags().StringVar(&opts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image. Pass an empty string \"\" to reset it.") cmd.Flags().StringVar(&opts.mode, "mode", api.ServiceModeReplicated, - fmt.Sprintf("Replication mode of the service: either %q (a specified number of containers across "+ - "the machines) or %q (one container on every machine).", + fmt.Sprintf("Replication mode of the service: either '%s' (a specified number of containers across "+ + "the machines) or '%s' (one container on every machine).", api.ServiceModeReplicated, api.ServiceModeGlobal)) cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil, "Placement constraint by machine name, limiting which machines the service can run on. Can be specified "+ @@ -68,6 +69,9 @@ func NewRunCommand() *cobra.Command { " -p app.example.com:8080/https Publish port 8080 as HTTPS via load balancer with custom hostname\n"+ " -p 9000:8080 Publish port 8080 as TCP port 9000 via load balancer\n"+ " -p 53:5353/udp@host Bind UDP port 5353 to host port 53") + cmd.Flags().StringVar(&opts.pull, "pull", api.PullPolicyMissing, + fmt.Sprintf("Pull image from the registry before running service containers ('%s', '%s', '%s').", + api.PullPolicyAlways, api.PullPolicyMissing, api.PullPolicyNever)) cmd.Flags().UintVar(&opts.replicas, "replicas", 1, "Number of containers to run for the service. Only valid for a replicated service.") cmd.Flags().StringSliceVarP(&opts.volumes, "volume", "v", nil, @@ -86,7 +90,13 @@ func run(ctx context.Context, uncli *cli.CLI, opts runOptions) error { switch opts.mode { case api.ServiceModeReplicated, api.ServiceModeGlobal: default: - return fmt.Errorf("invalid replication mode: %q", opts.mode) + return fmt.Errorf("invalid replication mode: '%s'", opts.mode) + } + + switch opts.pull { + case api.PullPolicyAlways, api.PullPolicyMissing, api.PullPolicyNever: + default: + return fmt.Errorf("invalid pull policy: '%s'", opts.pull) } var machineFilter deploy.MachineFilter @@ -124,9 +134,10 @@ func run(ctx context.Context, uncli *cli.CLI, opts runOptions) error { spec := api.ServiceSpec{ Container: api.ContainerSpec{ - Command: opts.command, - Image: opts.image, - Volumes: opts.volumes, + Command: opts.command, + Image: opts.image, + PullPolicy: opts.pull, + Volumes: opts.volumes, }, Mode: opts.mode, Name: opts.name, diff --git a/pkg/client/container.go b/pkg/client/container.go index dc0a19a0..95e2ba31 100644 --- a/pkg/client/container.go +++ b/pkg/client/container.go @@ -110,10 +110,25 @@ func (cli *Client) CreateContainer( pw := progress.ContextWriter(ctx) eventID := fmt.Sprintf("Container %s on %s", containerName, machine.Machine.Name) - pw.Event(progress.CreatingEvent(eventID)) + + if spec.Container.PullPolicy == api.PullPolicyAlways { + if err = cli.pullImageWithProgress(ctx, config.Image, machine.Machine.Name, eventID); err != nil { + return resp, err + } + } + resp, err = cli.Docker.CreateContainer(ctx, config, hostConfig, netConfig, nil, containerName) if err != nil { + switch spec.Container.PullPolicy { + case api.PullPolicyAlways, api.PullPolicyNever: + return resp, err + case api.PullPolicyMissing: + default: + return resp, fmt.Errorf("unsupported pull policy: '%s'", spec.Container.PullPolicy) + } + + // Not found error is expected if the image is missing. if !dockerclient.IsErrNotFound(err) { return resp, err } diff --git a/pkg/client/deploy/strategy.go b/pkg/client/deploy/strategy.go index 53a8a4db..840bf7bf 100644 --- a/pkg/client/deploy/strategy.go +++ b/pkg/client/deploy/strategy.go @@ -48,6 +48,7 @@ func (s *RollingStrategy) Plan( // planReplicated creates a plan for a replicated service deployment. // For replicated services, we want to maintain a specific number of containers (replicas) across the available machines // in the cluster. +// TODO: schedule containers only on machines that contain the image if pull policy is set to 'never'. func (s *RollingStrategy) planReplicated( ctx context.Context, cli api.MachineClient, svc *api.Service, spec api.ServiceSpec, ) (Plan, error) {