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 }