github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/compose/compose.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 compose
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"io"
    23  	"os"
    24  	"path/filepath"
    25  
    26  	"github.com/containerd/containerd"
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    29  	"github.com/containerd/nerdctl/v2/pkg/cmd/volume"
    30  	"github.com/containerd/nerdctl/v2/pkg/composer"
    31  	"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser"
    32  	"github.com/containerd/nerdctl/v2/pkg/imgutil"
    33  	"github.com/containerd/nerdctl/v2/pkg/ipfs"
    34  	"github.com/containerd/nerdctl/v2/pkg/netutil"
    35  	"github.com/containerd/nerdctl/v2/pkg/referenceutil"
    36  	"github.com/containerd/nerdctl/v2/pkg/signutil"
    37  	"github.com/containerd/nerdctl/v2/pkg/strutil"
    38  	"github.com/containerd/platforms"
    39  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    40  )
    41  
    42  // New returns a new *composer.Composer.
    43  func New(client *containerd.Client, globalOptions types.GlobalCommandOptions, options composer.Options, stdout, stderr io.Writer) (*composer.Composer, error) {
    44  	cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	networkConfigs, err := cniEnv.NetworkList()
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	options.NetworkExists = func(netName string) (bool, error) {
    53  		for _, f := range networkConfigs {
    54  			if f.Name == netName {
    55  				return true, nil
    56  			}
    57  		}
    58  		return false, nil
    59  	}
    60  
    61  	options.NetworkInUse = func(ctx context.Context, netName string) (bool, error) {
    62  		networkUsedByNsMap, err := netutil.UsedNetworks(ctx, client)
    63  		if err != nil {
    64  			return false, err
    65  		}
    66  		for _, v := range networkUsedByNsMap {
    67  			if strutil.InStringSlice(v, netName) {
    68  				return true, nil
    69  			}
    70  		}
    71  		return false, nil
    72  	}
    73  
    74  	volStore, err := volume.Store(globalOptions.Namespace, globalOptions.DataRoot, globalOptions.Address)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	options.VolumeExists = func(volName string) (bool, error) {
    79  		_, volGetErr := volStore.Get(volName, false)
    80  		if volGetErr == nil {
    81  			return true, nil
    82  		} else if errors.Is(volGetErr, errdefs.ErrNotFound) {
    83  			return false, nil
    84  		}
    85  		return false, volGetErr
    86  	}
    87  
    88  	options.ImageExists = func(ctx context.Context, rawRef string) (bool, error) {
    89  		refNamed, err := referenceutil.ParseAny(rawRef)
    90  		if err != nil {
    91  			return false, err
    92  		}
    93  		ref := refNamed.String()
    94  		if _, err := client.ImageService().Get(ctx, ref); err != nil {
    95  			if errors.Is(err, errdefs.ErrNotFound) {
    96  				return false, nil
    97  			}
    98  			return false, err
    99  		}
   100  		return true, nil
   101  	}
   102  
   103  	options.EnsureImage = func(ctx context.Context, imageName, pullMode, platform string, ps *serviceparser.Service, quiet bool) error {
   104  		ocispecPlatforms := []ocispec.Platform{platforms.DefaultSpec()}
   105  		if platform != "" {
   106  			parsed, err := platforms.Parse(platform)
   107  			if err != nil {
   108  				return err
   109  			}
   110  			ocispecPlatforms = []ocispec.Platform{parsed} // no append
   111  		}
   112  
   113  		// IPFS reference
   114  		if scheme, ref, err := referenceutil.ParseIPFSRefWithScheme(imageName); err == nil {
   115  			var ipfsPath string
   116  			if ipfsAddress := options.IPFSAddress; ipfsAddress != "" {
   117  				dir, err := os.MkdirTemp("", "apidirtmp")
   118  				if err != nil {
   119  					return err
   120  				}
   121  				defer os.RemoveAll(dir)
   122  				if err := os.WriteFile(filepath.Join(dir, "api"), []byte(ipfsAddress), 0600); err != nil {
   123  					return err
   124  				}
   125  				ipfsPath = dir
   126  			}
   127  			_, err = ipfs.EnsureImage(ctx, client, stdout, stderr, globalOptions.Snapshotter, scheme, ref,
   128  				pullMode, ocispecPlatforms, nil, quiet, ipfsPath, types.RemoteSnapshotterFlags{})
   129  			return err
   130  		}
   131  
   132  		imageVerifyOptions := imageVerifyOptionsFromCompose(ps)
   133  		ref, err := signutil.Verify(ctx, imageName, globalOptions.HostsDir, globalOptions.Experimental, imageVerifyOptions)
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		_, err = imgutil.EnsureImage(ctx, client, stdout, stderr, globalOptions.Snapshotter, ref,
   139  			pullMode, globalOptions.InsecureRegistry, globalOptions.HostsDir, ocispecPlatforms, nil, quiet, types.RemoteSnapshotterFlags{})
   140  		return err
   141  	}
   142  
   143  	return composer.New(options, client)
   144  }
   145  
   146  func imageVerifyOptionsFromCompose(ps *serviceparser.Service) types.ImageVerifyOptions {
   147  	var opt types.ImageVerifyOptions
   148  	if verifier, ok := ps.Unparsed.Extensions[serviceparser.ComposeVerify]; ok {
   149  		opt.Provider = verifier.(string)
   150  	} else {
   151  		opt.Provider = "none"
   152  	}
   153  
   154  	// for cosign, if key is given, use key mode, otherwise use keyless mode.
   155  	if keyVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPublicKey]; ok {
   156  		opt.CosignKey = keyVal.(string)
   157  	}
   158  	if ciVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateIdentity]; ok {
   159  		opt.CosignCertificateIdentity = ciVal.(string)
   160  	}
   161  	if cirVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateIdentityRegexp]; ok {
   162  		opt.CosignCertificateIdentityRegexp = cirVal.(string)
   163  	}
   164  	if coiVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateOidcIssuer]; ok {
   165  		opt.CosignCertificateOidcIssuer = coiVal.(string)
   166  	}
   167  	if coirVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateOidcIssuerRegexp]; ok {
   168  		opt.CosignCertificateOidcIssuerRegexp = coirVal.(string)
   169  	}
   170  	return opt
   171  }