mirror of https://github.com/docker/cli.git
Merge pull request #205 from redpanda/rollback
Add 'docker service rollback' subcommand
This commit is contained in:
commit
3c7ede6a68
|
@ -5,17 +5,18 @@ import (
|
|||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"golang.org/x/net/context"
|
||||
// Import builders to get the builder function as package function
|
||||
. "github.com/docker/cli/cli/internal/test/builders"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
|
||||
serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
|
||||
}
|
||||
|
||||
func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
if f.serviceListFunc != nil {
|
||||
return f.serviceListFunc(ctx, options)
|
||||
}
|
||||
func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -23,10 +24,30 @@ func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
if f.serviceInspectWithRawFunc != nil {
|
||||
return f.serviceInspectWithRawFunc(ctx, serviceID, options)
|
||||
}
|
||||
|
||||
return *Service(ServiceID(serviceID)), []byte{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
if f.serviceListFunc != nil {
|
||||
return f.serviceListFunc(ctx, options)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
|
||||
if f.serviceUpdateFunc != nil {
|
||||
return f.serviceUpdateFunc(ctx, serviceID, version, service, options)
|
||||
}
|
||||
|
||||
return types.ServiceUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
func newService(id string, name string) swarm.Service {
|
||||
return swarm.Service{
|
||||
ID: id,
|
||||
|
|
|
@ -26,6 +26,7 @@ func NewServiceCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
newScaleCommand(dockerCli),
|
||||
newUpdateCommand(dockerCli),
|
||||
newLogsCommand(dockerCli),
|
||||
newRollbackCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := newServiceOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -62,7 +62,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *serviceOptions) error {
|
||||
func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *serviceOptions) error {
|
||||
apiClient := dockerCli.Client()
|
||||
createOpts := types.ServiceCreateOptions{}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
// waitOnService waits for the service to converge. It outputs a progress bar,
|
||||
// if appropriate based on the CLI flags.
|
||||
func waitOnService(ctx context.Context, dockerCli *command.DockerCli, serviceID string, quiet bool) error {
|
||||
func waitOnService(ctx context.Context, dockerCli command.Cli, serviceID string, quiet bool) error {
|
||||
errChan := make(chan error, 1)
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ type inspectOptions struct {
|
|||
pretty bool
|
||||
}
|
||||
|
||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -43,7 +43,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm SERVICE [SERVICE...]",
|
||||
|
@ -27,7 +27,7 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(dockerCli *command.DockerCli, sids []string) error {
|
||||
func runRemove(dockerCli command.Cli, sids []string) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := newServiceOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rollback [OPTIONS] SERVICE",
|
||||
Short: "Revert changes to a service's configuration",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRollback(dockerCli, cmd.Flags(), options, args[0])
|
||||
},
|
||||
Tags: map[string]string{"version": "1.31"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.quiet, flagQuiet, "q", false, "Suppress progress output")
|
||||
addDetachFlag(flags, &options.detach)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRollback(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
|
||||
apiClient := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec := &service.Spec
|
||||
updateOpts := types.ServiceUpdateOptions{
|
||||
Rollback: "previous",
|
||||
}
|
||||
|
||||
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, warning := range response.Warnings {
|
||||
fmt.Fprintln(dockerCli.Err(), warning)
|
||||
}
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
|
||||
|
||||
if options.detach {
|
||||
warnDetachDefault(dockerCli.Err(), apiClient.ClientVersion(), flags, "rolled back")
|
||||
return nil
|
||||
}
|
||||
|
||||
return waitOnService(ctx, dockerCli, serviceID, options.quiet)
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestRollback(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
|
||||
expectedDockerCliErr string
|
||||
}{
|
||||
{
|
||||
name: "rollback-service",
|
||||
args: []string{"service-id"},
|
||||
},
|
||||
{
|
||||
name: "rollback-service-with-warnings",
|
||||
args: []string{"service-id"},
|
||||
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
|
||||
response := types.ServiceUpdateResponse{}
|
||||
|
||||
response.Warnings = []string{
|
||||
"- warning 1",
|
||||
"- warning 2",
|
||||
}
|
||||
|
||||
return response, nil
|
||||
},
|
||||
expectedDockerCliErr: "- warning 1\n- warning 2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceUpdateFunc: tc.serviceUpdateFunc,
|
||||
})
|
||||
cmd := newRollbackCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.Flags().Set("quiet", "true")
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.Equal(t, strings.TrimSpace(cli.ErrBuffer().String()), tc.expectedDockerCliErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollbackWithErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "not-enough-args",
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"service-id-1", "service-id-2"},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "service-does-not-exists",
|
||||
args: []string{"service-id"},
|
||||
serviceInspectWithRawFunc: func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return swarm.Service{}, []byte{}, fmt.Errorf("no such services: %s", serviceID)
|
||||
},
|
||||
expectedError: "no such services: service-id",
|
||||
},
|
||||
{
|
||||
name: "service-update-failed",
|
||||
args: []string{"service-id"},
|
||||
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
|
||||
return types.ServiceUpdateResponse{}, fmt.Errorf("no such services: %s", serviceID)
|
||||
},
|
||||
expectedError: "no such services: service-id",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cmd := newRollbackCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
serviceInspectWithRawFunc: tc.serviceInspectWithRawFunc,
|
||||
serviceUpdateFunc: tc.serviceUpdateFunc,
|
||||
}))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.Flags().Set("quiet", "true")
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ type scaleOptions struct {
|
|||
detach bool
|
||||
}
|
||||
|
||||
func newScaleCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newScaleCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := &scaleOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -54,7 +54,7 @@ func scaleArgs(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func runScale(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *scaleOptions, args []string) error {
|
||||
func runScale(dockerCli command.Cli, flags *pflag.FlagSet, options *scaleOptions, args []string) error {
|
||||
var errs []string
|
||||
var serviceIDs []string
|
||||
ctx := context.Background()
|
||||
|
@ -96,7 +96,7 @@ func runScale(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *scale
|
|||
return errors.Errorf(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
func runServiceScale(ctx context.Context, dockerCli *command.DockerCli, serviceID string, scale uint64) error {
|
||||
func runServiceScale(ctx context.Context, dockerCli command.Cli, serviceID string, scale uint64) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := newServiceOptions()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -103,7 +103,7 @@ func newListOptsVar() *opts.ListOpts {
|
|||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
|
||||
func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
|
||||
apiClient := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
@ -875,9 +875,10 @@ x3ti0erg11rjpg64m75kej2mz-hosttempl
|
|||
* [service inspect](service_inspect.md)
|
||||
* [service logs](service_logs.md)
|
||||
* [service ls](service_ls.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service update](service_update.md)
|
||||
|
||||
<style>table tr > td:first-child { white-space: nowrap;}</style>
|
||||
|
|
|
@ -164,7 +164,8 @@ $ docker service inspect --format='{{.Spec.Mode.Replicated.Replicas}}' redis
|
|||
* [service create](service_create.md)
|
||||
* [service logs](service_logs.md)
|
||||
* [service ls](service_ls.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service update](service_update.md)
|
||||
|
|
|
@ -79,7 +79,8 @@ fraction of a second no more than nine digits long. You can combine the
|
|||
* [service create](service_create.md)
|
||||
* [service inspect](service_inspect.md)
|
||||
* [service ls](service_ls.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service update](service_update.md)
|
||||
|
|
|
@ -158,7 +158,8 @@ fm6uf97exkul: global 5/5
|
|||
* [service create](service_create.md)
|
||||
* [service inspect](service_inspect.md)
|
||||
* [service logs](service_logs.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service update](service_update.md)
|
||||
|
|
|
@ -190,5 +190,6 @@ top.3: busybox
|
|||
* [service logs](service_logs.md)
|
||||
* [service ls](service_ls.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service update](service_update.md)
|
||||
|
|
|
@ -55,6 +55,7 @@ ID NAME MODE REPLICAS IMAGE
|
|||
* [service inspect](service_inspect.md)
|
||||
* [service logs](service_logs.md)
|
||||
* [service ls](service_ls.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service update](service_update.md)
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
---
|
||||
title: "service rollback"
|
||||
description: "The service rollback command description and usage"
|
||||
keywords: "service, rollback"
|
||||
---
|
||||
|
||||
<!-- This file is maintained within the docker/cli Github
|
||||
repository at https://github.com/docker/cli/. Make all
|
||||
pull requests against that repo. If you see this file in
|
||||
another repository, consider it read-only there, as it will
|
||||
periodically be overwritten by the definitive file. Pull
|
||||
requests which include edits to this file in other repositories
|
||||
will be rejected.
|
||||
-->
|
||||
|
||||
# service rollback
|
||||
|
||||
```markdown
|
||||
Usage: docker service rollback SERVICE
|
||||
|
||||
Revert changes to a service's configuration
|
||||
|
||||
Options:
|
||||
-d, --detach Exit immediately instead of waiting for the service to converge (default true)
|
||||
--help Print usage
|
||||
-q, --quiet Suppress progress output
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
Roll back a specified service to its previous version from the swarm. This command must be run
|
||||
targeting a manager node.
|
||||
|
||||
## Examples
|
||||
|
||||
### Roll back to the previous version of a service
|
||||
|
||||
Use the `docker service rollback` command to roll back to the previous version
|
||||
of a service. After executing this command, the service is reverted to the
|
||||
configuration that was in place before the most recent `docker service update`
|
||||
command.
|
||||
|
||||
The following example creates a service with a single replica, updates the
|
||||
service to use three replicas, and then rolls back the service to the
|
||||
previous version, having one replica.
|
||||
|
||||
Create a service with a single replica:
|
||||
|
||||
```bash
|
||||
$ docker service create --name my-service -p 8080:80 nginx:alpine
|
||||
```
|
||||
|
||||
Confirm that the service is running with a single replica:
|
||||
|
||||
```bash
|
||||
$ docker service ls
|
||||
|
||||
ID NAME MODE REPLICAS IMAGE PORTS
|
||||
xbw728mf6q0d my-service replicated 1/1 nginx:alpine *:8080->80/tcp
|
||||
```
|
||||
|
||||
Update the service to use three replicas:
|
||||
|
||||
```bash
|
||||
$ docker service update --replicas=3 my-service
|
||||
|
||||
$ docker service ls
|
||||
|
||||
ID NAME MODE REPLICAS IMAGE PORTS
|
||||
xbw728mf6q0d my-service replicated 3/3 nginx:alpine *:8080->80/tcp
|
||||
```
|
||||
|
||||
Now roll back the service to its previous version, and confirm it is
|
||||
running a single replica again:
|
||||
|
||||
```bash
|
||||
$ docker service rollback my-service
|
||||
|
||||
$ docker service ls
|
||||
|
||||
ID NAME MODE REPLICAS IMAGE PORTS
|
||||
xbw728mf6q0d my-service replicated 1/1 nginx:alpine *:8080->80/tcp
|
||||
```
|
||||
|
||||
## Related commands
|
||||
|
||||
* [service create](service_create.md)
|
||||
* [service inspect](service_inspect.md)
|
||||
* [service logs](service_logs.md)
|
||||
* [service ls](service_ls.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service scale](service_scale.md)
|
||||
* [service update](service_update.md)
|
|
@ -101,5 +101,6 @@ ID NAME MODE REPLICAS IMAGE
|
|||
* [service logs](service_logs.md)
|
||||
* [service ls](service_ls.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service update](service_update.md)
|
||||
|
|
|
@ -174,7 +174,7 @@ $ docker service update --mount-rm /somewhere myservice
|
|||
myservice
|
||||
```
|
||||
|
||||
### Rolling back to the previous version of a service
|
||||
### Roll back to the previous version of a service
|
||||
|
||||
Use the `--rollback` option to roll back to the previous version of the service.
|
||||
|
||||
|
@ -266,4 +266,5 @@ See [`service create`](./service_create.md#templating) for the reference.
|
|||
* [service ls](service_ls.md)
|
||||
* [service ps](service_ps.md)
|
||||
* [service rm](service_rm.md)
|
||||
* [service rollback](service_rollback.md)
|
||||
* [service scale](service_scale.md)
|
||||
|
|
Loading…
Reference in New Issue