github.com/containerd/nerdctl@v1.7.7/pkg/composer/up.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  	"fmt"
    22  	"os"
    23  
    24  	"github.com/compose-spec/compose-go/types"
    25  	"github.com/containerd/log"
    26  	"github.com/containerd/nerdctl/pkg/composer/serviceparser"
    27  	"github.com/containerd/nerdctl/pkg/reflectutil"
    28  )
    29  
    30  type UpOptions struct {
    31  	Detach        bool
    32  	NoBuild       bool
    33  	NoColor       bool
    34  	NoLogPrefix   bool
    35  	ForceBuild    bool
    36  	IPFS          bool
    37  	QuietPull     bool
    38  	RemoveOrphans bool
    39  	Scale         map[string]uint64 // map of service name to replicas
    40  }
    41  
    42  func (c *Composer) Up(ctx context.Context, uo UpOptions, services []string) error {
    43  	for shortName := range c.project.Networks {
    44  		if err := c.upNetwork(ctx, shortName); err != nil {
    45  			return err
    46  		}
    47  	}
    48  
    49  	for shortName := range c.project.Volumes {
    50  		if err := c.upVolume(ctx, shortName); err != nil {
    51  			return err
    52  		}
    53  	}
    54  
    55  	for shortName, secret := range c.project.Secrets {
    56  		obj := types.FileObjectConfig(secret)
    57  		if err := validateFileObjectConfig(obj, shortName, "service", c.project); err != nil {
    58  			return err
    59  		}
    60  	}
    61  
    62  	for shortName, config := range c.project.Configs {
    63  		obj := types.FileObjectConfig(config)
    64  		if err := validateFileObjectConfig(obj, shortName, "config", c.project); err != nil {
    65  			return err
    66  		}
    67  	}
    68  
    69  	var parsedServices []*serviceparser.Service
    70  	// use WithServices to sort the services in dependency order
    71  	if err := c.project.WithServices(services, func(svc types.ServiceConfig) error {
    72  		if replicas, ok := uo.Scale[svc.Name]; ok {
    73  			if svc.Deploy == nil {
    74  				svc.Deploy = &types.DeployConfig{}
    75  			}
    76  			svc.Deploy.Replicas = &replicas
    77  		}
    78  		ps, err := serviceparser.Parse(c.project, svc)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		parsedServices = append(parsedServices, ps)
    83  		return nil
    84  	}); err != nil {
    85  		return err
    86  	}
    87  
    88  	// remove orphan containers before the service has be started
    89  	// FYI: https://github.com/docker/compose/blob/v2.3.4/pkg/compose/create.go#L91-L112
    90  	orphans, err := c.getOrphanContainers(ctx, parsedServices)
    91  	if err != nil && uo.RemoveOrphans {
    92  		return fmt.Errorf("error getting orphaned containers: %s", err)
    93  	}
    94  	if len(orphans) > 0 {
    95  		if uo.RemoveOrphans {
    96  			if err := c.removeContainers(ctx, orphans, RemoveOptions{Stop: true, Volumes: true}); err != nil {
    97  				return fmt.Errorf("error removing orphaned containers: %s", err)
    98  			}
    99  		} else {
   100  			log.G(ctx).Warnf("found %d orphaned containers: %v, you can run this command with the --remove-orphans flag to clean it up", len(orphans), orphans)
   101  		}
   102  	}
   103  
   104  	return c.upServices(ctx, parsedServices, uo)
   105  }
   106  
   107  func validateFileObjectConfig(obj types.FileObjectConfig, shortName, objType string, project *types.Project) error {
   108  	if unknown := reflectutil.UnknownNonEmptyFields(&obj, "Name", "External", "File"); len(unknown) > 0 {
   109  		log.L.Warnf("Ignoring: %s %s: %+v", objType, shortName, unknown)
   110  	}
   111  	if obj.External.External || obj.External.Name != "" {
   112  		return fmt.Errorf("%s %q: external object is not supported", objType, shortName)
   113  	}
   114  	if obj.File == "" {
   115  		return fmt.Errorf("%s %q: lacks file path", objType, shortName)
   116  	}
   117  	fullPath := project.RelativePath(obj.File)
   118  	if _, err := os.Stat(fullPath); err != nil {
   119  		return fmt.Errorf("%s %q: failed to open file %q: %w", objType, shortName, fullPath, err)
   120  	}
   121  	return nil
   122  }