Skip to content

Commit 69de5dd

Browse files
committed
feat: add force-recreate and no-recreate for compose up command
Signed-off-by: Justin Alvarez <[email protected]>
1 parent 6ff09eb commit 69de5dd

File tree

8 files changed

+117
-30
lines changed

8 files changed

+117
-30
lines changed

cmd/nerdctl/compose/compose_create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func composeCreateAction(cmd *cobra.Command, args []string) error {
6565
}
6666
noRecreate, err := cmd.Flags().GetBool("no-recreate")
6767
if err != nil {
68-
return nil
68+
return err
6969
}
7070
if forceRecreate && noRecreate {
7171
return errors.New("flag --force-recreate and --no-recreate cannot be specified together")

cmd/nerdctl/compose/compose_up.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ func newComposeUpCommand() *cobra.Command {
4747
composeUpCommand.Flags().Bool("ipfs", false, "Allow pulling base images from IPFS during build")
4848
composeUpCommand.Flags().Bool("quiet-pull", false, "Pull without printing progress information")
4949
composeUpCommand.Flags().Bool("remove-orphans", false, "Remove containers for services not defined in the Compose file.")
50+
composeUpCommand.Flags().Bool("force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
51+
composeUpCommand.Flags().Bool("no-recreate", false, "Don't recreate containers if they exist, conflict with --force-recreate.")
5052
composeUpCommand.Flags().StringArray("scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
5153
return composeUpCommand
5254
}
@@ -102,6 +104,17 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
102104
if err != nil {
103105
return err
104106
}
107+
forceRecreate, err := cmd.Flags().GetBool("force-recreate")
108+
if err != nil {
109+
return err
110+
}
111+
noRecreate, err := cmd.Flags().GetBool("no-recreate")
112+
if err != nil {
113+
return err
114+
}
115+
if forceRecreate && noRecreate {
116+
return errors.New("flag --force-recreate and --no-recreate cannot be specified together")
117+
}
105118
scale := make(map[string]int)
106119
for _, s := range scaleSlice {
107120
parts := strings.Split(s, "=")
@@ -141,6 +154,8 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
141154
QuietPull: quietPull,
142155
RemoveOrphans: removeOrphans,
143156
Scale: scale,
157+
ForceRecreate: forceRecreate,
158+
NoRecreate: noRecreate,
144159
}
145160
return c.Up(ctx, uo, services)
146161
}

docs/command-reference.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1410,8 +1410,10 @@ Flags:
14101410
- :whale: `--quiet-pull`: Pull without printing progress information
14111411
- :whale: `--scale`: Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
14121412
- :whale: `--remove-orphans`: Remove containers for services not defined in the Compose file
1413+
- :whale: `--force-recreate`: force Compose to stop and recreate all containers
1414+
- :whale: `--no-recreate`: force Compose to reuse existing containers
14131415

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

14171419
Unimplemented `docker compose up` (V2) flags: `--environment`

pkg/composer/container.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,24 @@ func (c *Composer) containerExists(ctx context.Context, name, service string) (b
6363
// container doesn't exist
6464
return false, nil
6565
}
66+
67+
func (c *Composer) containerID(ctx context.Context, name, service string) (string, error) {
68+
// get list of containers for service
69+
containers, err := c.Containers(ctx, service)
70+
if err != nil {
71+
return "", err
72+
}
73+
74+
for _, container := range containers {
75+
containerLabels, err := container.Labels(ctx)
76+
if err != nil {
77+
return "", err
78+
}
79+
if name == containerLabels[labels.Name] {
80+
// container exists
81+
return container.ID(), nil
82+
}
83+
}
84+
// container doesn't exist
85+
return "", nil
86+
}

pkg/composer/logs.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type LogsOptions struct {
4141
Tail string
4242
NoColor bool
4343
NoLogPrefix bool
44+
LatestRun bool
4445
}
4546

4647
func (c *Composer) Logs(ctx context.Context, lo LogsOptions, services []string) error {
@@ -62,9 +63,10 @@ func (c *Composer) Logs(ctx context.Context, lo LogsOptions, services []string)
6263
func (c *Composer) logs(ctx context.Context, containers []containerd.Container, lo LogsOptions) error {
6364
var logTagMaxLen int
6465
type containerState struct {
65-
name string
66-
logTag string
67-
logCmd *exec.Cmd
66+
name string
67+
logTag string
68+
logCmd *exec.Cmd
69+
startedAt string
6870
}
6971

7072
containerStates := make(map[string]containerState, len(containers)) // key: containerID
@@ -78,9 +80,15 @@ func (c *Composer) logs(ctx context.Context, containers []containerd.Container,
7880
if l := len(logTag); l > logTagMaxLen {
7981
logTagMaxLen = l
8082
}
83+
ts, err := info.UpdatedAt.MarshalText()
84+
if err != nil {
85+
return err
86+
}
87+
8188
containerStates[container.ID()] = containerState{
82-
name: name,
83-
logTag: logTag,
89+
name: name,
90+
logTag: logTag,
91+
startedAt: string(ts),
8492
}
8593
}
8694

@@ -102,6 +110,9 @@ func (c *Composer) logs(ctx context.Context, containers []containerd.Container,
102110
args = append(args, lo.Tail)
103111
}
104112
}
113+
if lo.LatestRun {
114+
args = append(args, fmt.Sprintf("--since=%s", state.startedAt))
115+
}
105116

106117
args = append(args, id)
107118
state.logCmd = c.createNerdctlCmd(ctx, args...)

pkg/composer/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ func (c *Composer) runServices(ctx context.Context, parsedServices []*servicepar
242242
container := ps.Containers[0]
243243

244244
runEG.Go(func() error {
245-
id, err := c.upServiceContainer(ctx, ps, container)
245+
id, err := c.upServiceContainer(ctx, ps, container, RecreateForce)
246246
if err != nil {
247247
return err
248248
}

pkg/composer/up.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,22 @@ type UpOptions struct {
3939
IPFS bool
4040
QuietPull bool
4141
RemoveOrphans bool
42+
ForceRecreate bool
43+
NoRecreate bool
4244
Scale map[string]int // map of service name to replicas
4345
}
4446

47+
func (opts UpOptions) recreateStrategy() string {
48+
switch {
49+
case opts.ForceRecreate:
50+
return RecreateForce
51+
case opts.NoRecreate:
52+
return RecreateNever
53+
default:
54+
return RecreateDiverged
55+
}
56+
}
57+
4558
func (c *Composer) Up(ctx context.Context, uo UpOptions, services []string) error {
4659
for shortName := range c.project.Networks {
4760
if err := c.upNetwork(ctx, shortName); err != nil {

pkg/composer/up_service.go

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"errors"
2222
"fmt"
2323
"os"
24+
"os/exec"
2425
"path/filepath"
2526
"strings"
2627
"sync"
@@ -45,6 +46,8 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars
4546
}
4647
}
4748

49+
recreate := uo.recreateStrategy()
50+
4851
var (
4952
containers = make(map[string]serviceparser.Container) // key: container ID
5053
services = []string{}
@@ -57,7 +60,7 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars
5760
for _, container := range ps.Containers {
5861
container := container
5962
runEG.Go(func() error {
60-
id, err := c.upServiceContainer(ctx, ps, container)
63+
id, err := c.upServiceContainer(ctx, ps, container, recreate)
6164
if err != nil {
6265
return err
6366
}
@@ -87,6 +90,7 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars
8790
Follow: true,
8891
NoColor: uo.NoColor,
8992
NoLogPrefix: uo.NoLogPrefix,
93+
LatestRun: recreate == RecreateNever,
9094
}
9195
if err := c.Logs(ctx, lo, services); err != nil {
9296
return err
@@ -118,15 +122,35 @@ func (c *Composer) ensureServiceImage(ctx context.Context, ps *serviceparser.Ser
118122

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

132+
// FIXME
133+
if service.Unparsed.StdinOpen != service.Unparsed.Tty {
134+
return "", fmt.Errorf("currently StdinOpen(-i) and Tty(-t) should be same")
135+
}
136+
137+
var runFlagD bool
138+
if !service.Unparsed.StdinOpen && !service.Unparsed.Tty {
139+
container.RunArgs = append([]string{"-d"}, container.RunArgs...)
140+
runFlagD = true
141+
}
142+
143+
// start the existing container and exit early
144+
if existingCid != "" && recreate == RecreateNever {
145+
cmd := c.createNerdctlCmd(ctx, append([]string{"start"}, existingCid)...)
146+
if err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {
147+
return "", fmt.Errorf("error while starting existing container %s: %w", container.Name, err)
148+
}
149+
return existingCid, nil
150+
}
151+
128152
// delete container if it already exists
129-
if exists {
153+
if existingCid != "" {
130154
log.G(ctx).Debugf("Container %q already exists, deleting", container.Name)
131155
delCmd := c.createNerdctlCmd(ctx, "rm", "-f", container.Name)
132156
if err = delCmd.Run(); err != nil {
@@ -151,12 +175,6 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
151175
defer os.RemoveAll(tempDir)
152176
cidFilename := filepath.Join(tempDir, "cid")
153177

154-
var runFlagD bool
155-
if !service.Unparsed.StdinOpen && !service.Unparsed.Tty {
156-
container.RunArgs = append([]string{"-d"}, container.RunArgs...)
157-
runFlagD = true
158-
}
159-
160178
//add metadata labels to container https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels
161179
container.RunArgs = append([]string{
162180
"--cidfile=" + cidFilename,
@@ -169,12 +187,24 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
169187
log.G(ctx).Debugf("Running %v", cmd.Args)
170188
}
171189

172-
// FIXME
173-
if service.Unparsed.StdinOpen != service.Unparsed.Tty {
174-
return "", fmt.Errorf("currently StdinOpen(-i) and Tty(-t) should be same")
190+
if err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {
191+
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
192+
}
193+
194+
cid, err := os.ReadFile(cidFilename)
195+
if err != nil {
196+
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
197+
}
198+
return strings.TrimSpace(string(cid)), nil
199+
}
200+
201+
func (c *Composer) executeUpCmd(ctx context.Context, cmd *exec.Cmd, containerName string, runFlagD, stdinOpen bool) error {
202+
log.G(ctx).Infof("Running %v", cmd.Args)
203+
if c.DebugPrintFull {
204+
log.G(ctx).Debugf("Running %v", cmd.Args)
175205
}
176206

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

186-
err = cmd.Run()
187-
if err != nil {
188-
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
216+
if err := cmd.Run(); err != nil {
217+
return fmt.Errorf("error while creating container %s: %w", containerName, err)
189218
}
190219

191-
cid, err := os.ReadFile(cidFilename)
192-
if err != nil {
193-
return "", fmt.Errorf("error while creating container %s: %w", container.Name, err)
194-
}
195-
return strings.TrimSpace(string(cid)), nil
220+
return nil
196221
}

0 commit comments

Comments
 (0)