Skip to content

Commit

Permalink
Merge pull request #3687 from pendo324/add-no-recreate-to-up
Browse files Browse the repository at this point in the history
feat: add force-recreate and no-recreate for compose up command
  • Loading branch information
AkihiroSuda authored Dec 3, 2024
2 parents 051b047 + 69de5dd commit eed4480
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 30 deletions.
2 changes: 1 addition & 1 deletion cmd/nerdctl/compose/compose_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func composeCreateAction(cmd *cobra.Command, args []string) error {
}
noRecreate, err := cmd.Flags().GetBool("no-recreate")
if err != nil {
return nil
return err
}
if forceRecreate && noRecreate {
return errors.New("flag --force-recreate and --no-recreate cannot be specified together")
Expand Down
15 changes: 15 additions & 0 deletions cmd/nerdctl/compose/compose_up.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func newComposeUpCommand() *cobra.Command {
composeUpCommand.Flags().Bool("ipfs", false, "Allow pulling base images from IPFS during build")
composeUpCommand.Flags().Bool("quiet-pull", false, "Pull without printing progress information")
composeUpCommand.Flags().Bool("remove-orphans", false, "Remove containers for services not defined in the Compose file.")
composeUpCommand.Flags().Bool("force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
composeUpCommand.Flags().Bool("no-recreate", false, "Don't recreate containers if they exist, conflict with --force-recreate.")
composeUpCommand.Flags().StringArray("scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
return composeUpCommand
}
Expand Down Expand Up @@ -102,6 +104,17 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
if err != nil {
return err
}
forceRecreate, err := cmd.Flags().GetBool("force-recreate")
if err != nil {
return err
}
noRecreate, err := cmd.Flags().GetBool("no-recreate")
if err != nil {
return err
}
if forceRecreate && noRecreate {
return errors.New("flag --force-recreate and --no-recreate cannot be specified together")
}
scale := make(map[string]int)
for _, s := range scaleSlice {
parts := strings.Split(s, "=")
Expand Down Expand Up @@ -141,6 +154,8 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
QuietPull: quietPull,
RemoveOrphans: removeOrphans,
Scale: scale,
ForceRecreate: forceRecreate,
NoRecreate: noRecreate,
}
return c.Up(ctx, uo, services)
}
4 changes: 3 additions & 1 deletion docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1410,8 +1410,10 @@ Flags:
- :whale: `--quiet-pull`: Pull without printing progress information
- :whale: `--scale`: Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
- :whale: `--remove-orphans`: Remove containers for services not defined in the Compose file
- :whale: `--force-recreate`: force Compose to stop and recreate all containers
- :whale: `--no-recreate`: force Compose to reuse existing containers

Unimplemented `docker-compose up` (V1) flags: `--no-deps`, `--force-recreate`, `--always-recreate-deps`, `--no-recreate`,
Unimplemented `docker-compose up` (V1) flags: `--no-deps`, `--always-recreate-deps`,
`--no-start`, `--abort-on-container-exit`, `--attach-dependencies`, `--timeout`, `--renew-anon-volumes`, `--exit-code-from`

Unimplemented `docker compose up` (V2) flags: `--environment`
Expand Down
21 changes: 21 additions & 0 deletions pkg/composer/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,24 @@ func (c *Composer) containerExists(ctx context.Context, name, service string) (b
// container doesn't exist
return false, nil
}

func (c *Composer) containerID(ctx context.Context, name, service string) (string, error) {
// get list of containers for service
containers, err := c.Containers(ctx, service)
if err != nil {
return "", err
}

for _, container := range containers {
containerLabels, err := container.Labels(ctx)
if err != nil {
return "", err
}
if name == containerLabels[labels.Name] {
// container exists
return container.ID(), nil
}
}
// container doesn't exist
return "", nil
}
21 changes: 16 additions & 5 deletions pkg/composer/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type LogsOptions struct {
Tail string
NoColor bool
NoLogPrefix bool
LatestRun bool
}

func (c *Composer) Logs(ctx context.Context, lo LogsOptions, services []string) error {
Expand Down Expand Up @@ -70,9 +71,10 @@ func (c *Composer) Logs(ctx context.Context, lo LogsOptions, services []string)
func (c *Composer) logs(ctx context.Context, containers []containerd.Container, lo LogsOptions) error {
var logTagMaxLen int
type containerState struct {
name string
logTag string
logCmd *exec.Cmd
name string
logTag string
logCmd *exec.Cmd
startedAt string
}

containerStates := make(map[string]containerState, len(containers)) // key: containerID
Expand All @@ -86,9 +88,15 @@ func (c *Composer) logs(ctx context.Context, containers []containerd.Container,
if l := len(logTag); l > logTagMaxLen {
logTagMaxLen = l
}
ts, err := info.UpdatedAt.MarshalText()
if err != nil {
return err
}

containerStates[container.ID()] = containerState{
name: name,
logTag: logTag,
name: name,
logTag: logTag,
startedAt: string(ts),
}
}

Expand All @@ -110,6 +118,9 @@ func (c *Composer) logs(ctx context.Context, containers []containerd.Container,
args = append(args, lo.Tail)
}
}
if lo.LatestRun {
args = append(args, fmt.Sprintf("--since=%s", state.startedAt))
}

args = append(args, id)
state.logCmd = c.createNerdctlCmd(ctx, args...)
Expand Down
2 changes: 1 addition & 1 deletion pkg/composer/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func (c *Composer) runServices(ctx context.Context, parsedServices []*servicepar
container := ps.Containers[0]

runEG.Go(func() error {
id, err := c.upServiceContainer(ctx, ps, container)
id, err := c.upServiceContainer(ctx, ps, container, RecreateForce)
if err != nil {
return err
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/composer/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,22 @@ type UpOptions struct {
IPFS bool
QuietPull bool
RemoveOrphans bool
ForceRecreate bool
NoRecreate bool
Scale map[string]int // map of service name to replicas
}

func (opts UpOptions) recreateStrategy() string {
switch {
case opts.ForceRecreate:
return RecreateForce
case opts.NoRecreate:
return RecreateNever
default:
return RecreateDiverged
}
}

func (c *Composer) Up(ctx context.Context, uo UpOptions, services []string) error {
for shortName := range c.project.Networks {
if err := c.upNetwork(ctx, shortName); err != nil {
Expand Down
69 changes: 47 additions & 22 deletions pkg/composer/up_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
Expand All @@ -45,6 +46,8 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars
}
}

recreate := uo.recreateStrategy()

var (
containers = make(map[string]serviceparser.Container) // key: container ID
services = []string{}
Expand All @@ -57,7 +60,7 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars
for _, container := range ps.Containers {
container := container
runEG.Go(func() error {
id, err := c.upServiceContainer(ctx, ps, container)
id, err := c.upServiceContainer(ctx, ps, container, recreate)
if err != nil {
return err
}
Expand Down Expand Up @@ -87,6 +90,7 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars
Follow: true,
NoColor: uo.NoColor,
NoLogPrefix: uo.NoLogPrefix,
LatestRun: recreate == RecreateNever,
}
if err := c.Logs(ctx, lo, services); err != nil {
return err
Expand Down Expand Up @@ -118,15 +122,35 @@ func (c *Composer) ensureServiceImage(ctx context.Context, ps *serviceparser.Ser

// upServiceContainer must be called after ensureServiceImage
// upServiceContainer returns container ID
func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparser.Service, container serviceparser.Container) (string, error) {
func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparser.Service, container serviceparser.Container, recreate string) (string, error) {
// check if container already exists
exists, err := c.containerExists(ctx, container.Name, service.Unparsed.Name)
existingCid, err := c.containerID(ctx, container.Name, service.Unparsed.Name)
if err != nil {
return "", fmt.Errorf("error while checking for containers with name %q: %s", container.Name, err)
}

// FIXME
if service.Unparsed.StdinOpen != service.Unparsed.Tty {
return "", fmt.Errorf("currently StdinOpen(-i) and Tty(-t) should be same")
}

var runFlagD bool
if !service.Unparsed.StdinOpen && !service.Unparsed.Tty {
container.RunArgs = append([]string{"-d"}, container.RunArgs...)
runFlagD = true
}

// start the existing container and exit early
if existingCid != "" && recreate == RecreateNever {
cmd := c.createNerdctlCmd(ctx, append([]string{"start"}, existingCid)...)
if err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {
return "", fmt.Errorf("error while starting existing container %s: %w", container.Name, err)
}
return existingCid, nil
}

// delete container if it already exists
if exists {
if existingCid != "" {
log.G(ctx).Debugf("Container %q already exists, deleting", container.Name)
delCmd := c.createNerdctlCmd(ctx, "rm", "-f", container.Name)
if err = delCmd.Run(); err != nil {
Expand All @@ -151,12 +175,6 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
defer os.RemoveAll(tempDir)
cidFilename := filepath.Join(tempDir, "cid")

var runFlagD bool
if !service.Unparsed.StdinOpen && !service.Unparsed.Tty {
container.RunArgs = append([]string{"-d"}, container.RunArgs...)
runFlagD = true
}

//add metadata labels to container https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels
container.RunArgs = append([]string{
"--cidfile=" + cidFilename,
Expand All @@ -169,12 +187,24 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
log.G(ctx).Debugf("Running %v", cmd.Args)
}

// FIXME
if service.Unparsed.StdinOpen != service.Unparsed.Tty {
return "", fmt.Errorf("currently StdinOpen(-i) and Tty(-t) should be same")
if err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
}

cid, err := os.ReadFile(cidFilename)
if err != nil {
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
}
return strings.TrimSpace(string(cid)), nil
}

func (c *Composer) executeUpCmd(ctx context.Context, cmd *exec.Cmd, containerName string, runFlagD, stdinOpen bool) error {
log.G(ctx).Infof("Running %v", cmd.Args)
if c.DebugPrintFull {
log.G(ctx).Debugf("Running %v", cmd.Args)
}

if service.Unparsed.StdinOpen {
if stdinOpen {
cmd.Stdin = os.Stdin
}
if !runFlagD {
Expand All @@ -183,14 +213,9 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
// Always propagate stderr to print detailed error messages (https://github.com/containerd/nerdctl/issues/1942)
cmd.Stderr = os.Stderr

err = cmd.Run()
if err != nil {
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
if err := cmd.Run(); err != nil {
return fmt.Errorf("error while creating container %s: %w", containerName, err)
}

cid, err := os.ReadFile(cidFilename)
if err != nil {
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
}
return strings.TrimSpace(string(cid)), nil
return nil
}

0 comments on commit eed4480

Please sign in to comment.