github.com/containerd/nerdctl@v1.7.7/pkg/composer/up_service.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package composer 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "os" 24 "path/filepath" 25 "strings" 26 "sync" 27 28 "github.com/containerd/log" 29 "github.com/containerd/nerdctl/pkg/composer/serviceparser" 30 "github.com/containerd/nerdctl/pkg/labels" 31 32 "golang.org/x/sync/errgroup" 33 ) 34 35 func (c *Composer) upServices(ctx context.Context, parsedServices []*serviceparser.Service, uo UpOptions) error { 36 if len(parsedServices) == 0 { 37 return errors.New("no service was provided") 38 } 39 40 // TODO: parallelize loop for ensuring images (make sure not to mess up tty) 41 for _, ps := range parsedServices { 42 if err := c.ensureServiceImage(ctx, ps, !uo.NoBuild, uo.ForceBuild, BuildOptions{}, uo.QuietPull); err != nil { 43 return err 44 } 45 } 46 47 var ( 48 containers = make(map[string]serviceparser.Container) // key: container ID 49 services = []string{} 50 containersMu sync.Mutex 51 ) 52 for _, ps := range parsedServices { 53 ps := ps 54 var runEG errgroup.Group 55 services = append(services, ps.Unparsed.Name) 56 for _, container := range ps.Containers { 57 container := container 58 runEG.Go(func() error { 59 id, err := c.upServiceContainer(ctx, ps, container) 60 if err != nil { 61 return err 62 } 63 containersMu.Lock() 64 containers[id] = container 65 containersMu.Unlock() 66 return nil 67 }) 68 } 69 if err := runEG.Wait(); err != nil { 70 return err 71 } 72 } 73 74 if uo.Detach { 75 return nil 76 } 77 78 log.G(ctx).Info("Attaching to logs") 79 lo := LogsOptions{ 80 Follow: true, 81 NoColor: uo.NoColor, 82 NoLogPrefix: uo.NoLogPrefix, 83 } 84 if err := c.Logs(ctx, lo, services); err != nil { 85 return err 86 } 87 88 log.G(ctx).Infof("Stopping containers (forcibly)") // TODO: support gracefully stopping 89 c.stopContainersFromParsedServices(ctx, containers) 90 return nil 91 } 92 93 func (c *Composer) ensureServiceImage(ctx context.Context, ps *serviceparser.Service, allowBuild, forceBuild bool, bo BuildOptions, quiet bool) error { 94 if ps.Build != nil && allowBuild { 95 if ps.Build.Force || forceBuild { 96 return c.buildServiceImage(ctx, ps.Image, ps.Build, ps.Unparsed.Platform, bo) 97 } 98 if ok, err := c.ImageExists(ctx, ps.Image); err != nil { 99 return err 100 } else if !ok { 101 return c.buildServiceImage(ctx, ps.Image, ps.Build, ps.Unparsed.Platform, bo) 102 } 103 // even when c.ImageExists returns true, we need to call c.EnsureImage 104 // because ps.PullMode can be "always". So no return here. 105 log.G(ctx).Debugf("Image %s already exists, not building", ps.Image) 106 } 107 108 log.G(ctx).Infof("Ensuring image %s", ps.Image) 109 return c.EnsureImage(ctx, ps.Image, ps.PullMode, ps.Unparsed.Platform, ps, quiet) 110 } 111 112 // upServiceContainer must be called after ensureServiceImage 113 // upServiceContainer returns container ID 114 func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparser.Service, container serviceparser.Container) (string, error) { 115 // check if container already exists 116 exists, err := c.containerExists(ctx, container.Name, service.Unparsed.Name) 117 if err != nil { 118 return "", fmt.Errorf("error while checking for containers with name %q: %s", container.Name, err) 119 } 120 121 // delete container if it already exists 122 if exists { 123 log.G(ctx).Debugf("Container %q already exists, deleting", container.Name) 124 delCmd := c.createNerdctlCmd(ctx, "rm", "-f", container.Name) 125 if err = delCmd.Run(); err != nil { 126 return "", fmt.Errorf("could not delete container %q: %s", container.Name, err) 127 } 128 log.G(ctx).Infof("Re-creating container %s", container.Name) 129 } else { 130 log.G(ctx).Infof("Creating container %s", container.Name) 131 } 132 133 for _, f := range container.Mkdir { 134 log.G(ctx).Debugf("Creating a directory %q", f) 135 if err = os.MkdirAll(f, 0o755); err != nil { 136 return "", fmt.Errorf("failed to create a directory %q: %w", f, err) 137 } 138 } 139 140 tempDir, err := os.MkdirTemp(os.TempDir(), "compose-") 141 if err != nil { 142 return "", fmt.Errorf("error while creating/re-creating container %s: %w", container.Name, err) 143 } 144 defer os.RemoveAll(tempDir) 145 cidFilename := filepath.Join(tempDir, "cid") 146 147 var runFlagD bool 148 if !service.Unparsed.StdinOpen && !service.Unparsed.Tty { 149 container.RunArgs = append([]string{"-d"}, container.RunArgs...) 150 runFlagD = true 151 } 152 153 //add metadata labels to container https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels 154 container.RunArgs = append([]string{ 155 "--cidfile=" + cidFilename, 156 fmt.Sprintf("-l=%s=%s", labels.ComposeProject, c.project.Name), 157 fmt.Sprintf("-l=%s=%s", labels.ComposeService, service.Unparsed.Name), 158 }, container.RunArgs...) 159 160 cmd := c.createNerdctlCmd(ctx, append([]string{"run"}, container.RunArgs...)...) 161 if c.DebugPrintFull { 162 log.G(ctx).Debugf("Running %v", cmd.Args) 163 } 164 165 // FIXME 166 if service.Unparsed.StdinOpen != service.Unparsed.Tty { 167 return "", fmt.Errorf("currently StdinOpen(-i) and Tty(-t) should be same") 168 } 169 170 if service.Unparsed.StdinOpen { 171 cmd.Stdin = os.Stdin 172 } 173 if !runFlagD { 174 cmd.Stdout = os.Stdout 175 } 176 // Always propagate stderr to print detailed error messages (https://github.com/containerd/nerdctl/issues/1942) 177 cmd.Stderr = os.Stderr 178 179 err = cmd.Run() 180 if err != nil { 181 return "", fmt.Errorf("error while creating container %s: %w", container.Name, err) 182 } 183 184 cid, err := os.ReadFile(cidFilename) 185 if err != nil { 186 return "", fmt.Errorf("error while creating container %s: %w", container.Name, err) 187 } 188 return strings.TrimSpace(string(cid)), nil 189 }