github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/compose_start.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 main 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 24 "github.com/containerd/containerd" 25 "github.com/containerd/errdefs" 26 "github.com/containerd/nerdctl/pkg/clientutil" 27 "github.com/containerd/nerdctl/pkg/cmd/compose" 28 "github.com/containerd/nerdctl/pkg/containerutil" 29 "github.com/containerd/nerdctl/pkg/labels" 30 "github.com/spf13/cobra" 31 "golang.org/x/sync/errgroup" 32 ) 33 34 func newComposeStartCommand() *cobra.Command { 35 var composeRestartCommand = &cobra.Command{ 36 Use: "start [SERVICE...]", 37 Short: "Start existing containers for service(s)", 38 RunE: composeStartAction, 39 SilenceUsage: true, 40 SilenceErrors: true, 41 DisableFlagsInUseLine: true, 42 } 43 return composeRestartCommand 44 } 45 46 func composeStartAction(cmd *cobra.Command, args []string) error { 47 globalOptions, err := processRootCmdFlags(cmd) 48 if err != nil { 49 return err 50 } 51 52 client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address) 53 if err != nil { 54 return err 55 } 56 defer cancel() 57 options, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental) 58 if err != nil { 59 return err 60 } 61 c, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr()) 62 if err != nil { 63 return err 64 } 65 66 // TODO(djdongjin): move to `pkg/composer` and rewrite `c.Services + for-loop` 67 // with `c.project.WithServices` after refactor (#1639) is done. 68 services, err := c.Services(ctx, args...) 69 if err != nil { 70 return err 71 } 72 for _, svc := range services { 73 svcName := svc.Unparsed.Name 74 containers, err := c.Containers(ctx, svcName) 75 if err != nil { 76 return err 77 } 78 // return error if no containers and service replica is not zero 79 if len(containers) == 0 { 80 if len(svc.Containers) == 0 { 81 continue 82 } 83 return fmt.Errorf("service %q has no container to start", svcName) 84 } 85 86 if err := startContainers(ctx, client, containers); err != nil { 87 return err 88 } 89 } 90 91 return nil 92 } 93 94 func startContainers(ctx context.Context, client *containerd.Client, containers []containerd.Container) error { 95 eg, ctx := errgroup.WithContext(ctx) 96 for _, c := range containers { 97 c := c 98 eg.Go(func() error { 99 if cStatus, err := containerutil.ContainerStatus(ctx, c); err != nil { 100 // NOTE: NotFound doesn't mean that container hasn't started. 101 // In docker/CRI-containerd plugin, the task will be deleted 102 // when it exits. So, the status will be "created" for this 103 // case. 104 if !errdefs.IsNotFound(err) { 105 return err 106 } 107 } else if cStatus.Status == containerd.Running { 108 return nil 109 } 110 111 // in compose, always disable attach 112 if err := containerutil.Start(ctx, c, false, client, ""); err != nil { 113 return err 114 } 115 info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata) 116 if err != nil { 117 return err 118 } 119 120 _, err = fmt.Fprintf(os.Stdout, "Container %s started\n", info.Labels[labels.Name]) 121 return err 122 }) 123 } 124 125 return eg.Wait() 126 }