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 compose restart command #1533

Merged
merged 2 commits into from
Nov 24, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ It does not necessarily mean that the corresponding features are missing in cont
- [:whale: nerdctl compose push](#whale-nerdctl-compose-push)
- [:whale: nerdctl compose config](#whale-nerdctl-compose-config)
- [:whale: nerdctl compose kill](#whale-nerdctl-compose-kill)
- [:whale: nerdctl compose restart](#whale-nerdctl-compose-restart)
- [:whale: nerdctl compose run](#whale-nerdctl-compose-run)
- [:whale: nerdctl compose version](#whale-nerdctl-compose-version)
- [IPFS management](#ipfs-management)
Expand Down Expand Up @@ -1503,6 +1504,16 @@ Usage: `nerdctl compose kill [OPTIONS] [SERVICE...]`
Flags:
- :whale: `-s, --signal`: SIGNAL to send to the container (default: "SIGKILL")

### :whale: nerdctl compose restart

Restart containers of given (or all) services

Usage: `nerdctl compose restart [OPTIONS] [SERVICE...]`

Flags:

- :whale: `-t, --timeout`: Seconds to wait before restarting it (default 10)

### :whale: nerdctl compose run
Run a one-off command on a service

Expand Down Expand Up @@ -1590,7 +1601,7 @@ Registry:
- `docker search`

Compose:
- `docker-compose create|events|exec|images|pause|port|restart|rm|scale|start|top|unpause`
- `docker-compose create|events|exec|images|pause|port|rm|scale|start|top|unpause`

Others:
- `docker system df`
Expand Down
1 change: 1 addition & 0 deletions cmd/nerdctl/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func newComposeCommand() *cobra.Command {
newComposeDownCommand(),
newComposePsCommand(),
newComposeKillCommand(),
newComposeRestartCommand(),
newComposeRunCommand(),
newComposeVersionCommand(),
newComposeStopCommand(),
Expand Down
58 changes: 58 additions & 0 deletions cmd/nerdctl/compose_restart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"github.com/containerd/nerdctl/pkg/composer"
"github.com/spf13/cobra"
)

func newComposeRestartCommand() *cobra.Command {
var composeRestartCommand = &cobra.Command{
Use: "restart [flags] [SERVICE...]",
Short: "Restart containers of given (or all) services",
RunE: composeRestartAction,
SilenceUsage: true,
SilenceErrors: true,
}
composeRestartCommand.Flags().UintP("timeout", "t", 10, "Seconds to wait before restarting them")
return composeRestartCommand
}

func composeRestartAction(cmd *cobra.Command, args []string) error {
var opt composer.RestartOptions

if cmd.Flags().Changed("timeout") {
timeValue, err := cmd.Flags().GetUint("timeout")
if err != nil {
return err
}
opt.Timeout = &timeValue
}

client, ctx, cancel, err := newClient(cmd)
if err != nil {
return err
}
defer cancel()

c, err := getComposer(cmd, client)
if err != nil {
return err
}
return c.Restart(ctx, opt, args)
}
98 changes: 98 additions & 0 deletions cmd/nerdctl/compose_restart_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"fmt"
"strings"
"testing"

"github.com/containerd/nerdctl/pkg/testutil"
)

func TestComposeRestart(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'

services:
wordpress:
image: %s
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- wordpress:/var/www/html
db:
image: %s
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql

volumes:
wordpress:
db:
`, testutil.WordpressImage, testutil.MariaDBImage)

comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)

base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()

exitAssertHandler := func(svc string) func(stdout string) error {
return func(stdout string) error {
// Docker Compose v1: "Exit code", v2: "exited (code)"
if !strings.Contains(stdout, "Exit") && !strings.Contains(stdout, "exited") {
return fmt.Errorf("service \"%s\" must have exited", svc)
}
return nil
}
}
upAssertHandler := func(svc string) func(stdout string) error {
return func(stdout string) error {
// Docker Compose v1: "Up", v2: "running"
if !strings.Contains(stdout, "Up") && !strings.Contains(stdout, "running") {
return fmt.Errorf("service \"%s\" must have been still running", svc)
}
return nil
}
}

// stop and restart a single service.
base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "db").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutWithFunc(exitAssertHandler("db"))
base.ComposeCmd("-f", comp.YAMLFullPath(), "restart", "db").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutWithFunc(upAssertHandler("db"))

// stop one service and restart all
base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "db").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutWithFunc(exitAssertHandler("db"))
base.ComposeCmd("-f", comp.YAMLFullPath(), "restart").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutWithFunc(upAssertHandler("db"))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress").AssertOutWithFunc(upAssertHandler("wordpress"))
}
3 changes: 1 addition & 2 deletions cmd/nerdctl/compose_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ func composeStopAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
opt.TimeChanged = true
opt.Timeout = timeValue
opt.Timeout = &timeValue
}

client, ctx, cancel, err := newClient(cmd)
Expand Down
2 changes: 2 additions & 0 deletions cmd/nerdctl/compose_stop_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ volumes:

comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)

base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
Expand Down
83 changes: 83 additions & 0 deletions pkg/composer/restart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright The containerd Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package composer

import (
"context"
"fmt"
"sync"

"github.com/containerd/containerd"
"github.com/containerd/nerdctl/pkg/labels"
"github.com/containerd/nerdctl/pkg/strutil"

"github.com/sirupsen/logrus"
)

// RestartOptions stores all option input from `nerdctl compose restart`
type RestartOptions struct {
Timeout *uint
}

// Restart restarts running/stopped containers in `services`. It calls
// `nerdctl restart CONTAINER_ID` to do the actual job.
func (c *Composer) Restart(ctx context.Context, opt RestartOptions, services []string) error {
serviceNames, err := c.ServiceNames(services...)
if err != nil {
return err
}
// reverse dependency order
for _, svc := range strutil.ReverseStrSlice(serviceNames) {
containers, err := c.Containers(ctx, svc)
if err != nil {
return err
}
if err := c.restartContainers(ctx, containers, opt); err != nil {
return err
}
}
return nil
}

func (c *Composer) restartContainers(ctx context.Context, containers []containerd.Container, opt RestartOptions) error {
var timeoutArg string
if opt.Timeout != nil {
timeoutArg = fmt.Sprintf("--timeout=%d", *opt.Timeout)
}

var rsWG sync.WaitGroup
for _, container := range containers {
container := container
rsWG.Add(1)
go func() {
defer rsWG.Done()
info, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)
logrus.Infof("Restarting container %s", info.Labels[labels.Name])
args := []string{"restart"}
if opt.Timeout != nil {
args = append(args, timeoutArg)
}
args = append(args, container.ID())
if err := c.runNerdctlCmd(ctx, args...); err != nil {
logrus.Warn(err)
}
}()
}
rsWG.Wait()

return nil
}
7 changes: 3 additions & 4 deletions pkg/composer/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ import (

// StopOptions stores all option input from `nerdctl compose stop`
type StopOptions struct {
TimeChanged bool
Timeout uint
Timeout *uint
}

// Stop stops containers in `services` without removing them. It calls
Expand All @@ -57,7 +56,7 @@ func (c *Composer) Stop(ctx context.Context, opt StopOptions, services []string)

func (c *Composer) stopContainers(ctx context.Context, containers []containerd.Container, opt StopOptions) error {
var timeoutArg string
if opt.TimeChanged {
if opt.Timeout != nil {
timeoutArg = fmt.Sprintf("--timeout=%d", opt.Timeout)
}

Expand All @@ -70,7 +69,7 @@ func (c *Composer) stopContainers(ctx context.Context, containers []containerd.C
info, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)
logrus.Infof("Stopping container %s", info.Labels[labels.Name])
args := []string{"stop"}
if opt.TimeChanged {
if opt.Timeout != nil {
args = append(args, timeoutArg)
}
args = append(args, container.ID())
Expand Down