Skip to content

Commit eed4480

Browse files
authored
Merge pull request containerd#3687 from pendo324/add-no-recreate-to-up
feat: add force-recreate and no-recreate for compose up command
2 parents 051b047 + 69de5dd commit eed4480

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 {
@@ -70,9 +71,10 @@ func (c *Composer) Logs(ctx context.Context, lo LogsOptions, services []string)
7071
func (c *Composer) logs(ctx context.Context, containers []containerd.Container, lo LogsOptions) error {
7172
var logTagMaxLen int
7273
type containerState struct {
73-
name string
74-
logTag string
75-
logCmd *exec.Cmd
74+
name string
75+
logTag string
76+
logCmd *exec.Cmd
77+
startedAt string
7678
}
7779

7880
containerStates := make(map[string]containerState, len(containers)) // key: containerID
@@ -86,9 +88,15 @@ func (c *Composer) logs(ctx context.Context, containers []containerd.Container,
8688
if l := len(logTag); l > logTagMaxLen {
8789
logTagMaxLen = l
8890
}
91+
ts, err := info.UpdatedAt.MarshalText()
92+
if err != nil {
93+
return err
94+
}
95+
8996
containerStates[container.ID()] = containerState{
90-
name: name,
91-
logTag: logTag,
97+
name: name,
98+
logTag: logTag,
99+
startedAt: string(ts),
92100
}
93101
}
94102

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

114125
args = append(args, id)
115126
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)