Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'docker service rollback' subcommand #205

Merged
merged 1 commit into from Aug 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 27 additions & 6 deletions cli/command/service/client_test.go
Expand Up @@ -5,28 +5,49 @@ 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
serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
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
}

func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
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,
Expand Down
1 change: 1 addition & 0 deletions cli/command/service/cmd.go
Expand Up @@ -26,6 +26,7 @@ func NewServiceCommand(dockerCli *command.DockerCli) *cobra.Command {
newScaleCommand(dockerCli),
newUpdateCommand(dockerCli),
newLogsCommand(dockerCli),
newRollbackCommand(dockerCli),
)
return cmd
}
4 changes: 2 additions & 2 deletions cli/command/service/create.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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{}

Expand Down
2 changes: 1 addition & 1 deletion cli/command/service/helpers.go
Expand Up @@ -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()

Expand Down
4 changes: 2 additions & 2 deletions cli/command/service/inspect.go
Expand Up @@ -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{
Expand All @@ -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()

Expand Down
4 changes: 2 additions & 2 deletions cli/command/service/remove.go
Expand Up @@ -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...]",
Expand All @@ -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()
Expand Down
65 changes: 65 additions & 0 deletions cli/command/service/rollback.go
@@ -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)
}
104 changes: 104 additions & 0 deletions cli/command/service/rollback_test.go
@@ -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)
}
}
6 changes: 3 additions & 3 deletions cli/command/service/scale.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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{})
Expand Down
4 changes: 2 additions & 2 deletions cli/command/service/update.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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()

Expand Down
3 changes: 2 additions & 1 deletion docs/reference/commandline/service_create.md
Expand Up @@ -877,9 +877,10 @@ x3ti0erg11rjpg64m75kej2mz-hosttempl
* [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 rollback](service_rollback.md)
* [service scale](service_scale.md)
* [service ps](service_ps.md)
* [service update](service_update.md)

<style>table tr > td:first-child { white-space: nowrap;}</style>
3 changes: 2 additions & 1 deletion docs/reference/commandline/service_inspect.md
Expand Up @@ -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 ps](service_ps.md)
* [service rm](service_rm.md)
* [service rollback](service_rollback.md)
* [service scale](service_scale.md)
* [service ps](service_ps.md)
* [service update](service_update.md)
3 changes: 2 additions & 1 deletion docs/reference/commandline/service_logs.md
Expand Up @@ -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 ps](service_ps.md)
* [service rm](service_rm.md)
* [service rollback](service_rollback.md)
* [service scale](service_scale.md)
* [service ps](service_ps.md)
* [service update](service_update.md)
3 changes: 2 additions & 1 deletion docs/reference/commandline/service_ls.md
Expand Up @@ -158,7 +158,8 @@ fm6uf97exkul: global 5/5
* [service create](service_create.md)
* [service inspect](service_inspect.md)
* [service logs](service_logs.md)
* [service ps](service_ps.md)
* [service rm](service_rm.md)
* [service rollback](service_rollback.md)
* [service scale](service_scale.md)
* [service ps](service_ps.md)
* [service update](service_update.md)
1 change: 1 addition & 0 deletions docs/reference/commandline/service_ps.md
Expand Up @@ -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)