github.com/containerd/nerdctl@v1.7.7/pkg/ipfs/image.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 ipfs 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "os" 24 25 "github.com/containerd/containerd" 26 "github.com/containerd/containerd/images" 27 "github.com/containerd/containerd/images/converter" 28 "github.com/containerd/containerd/remotes" 29 "github.com/containerd/log" 30 "github.com/containerd/nerdctl/pkg/idutil/imagewalker" 31 "github.com/containerd/nerdctl/pkg/imgutil" 32 "github.com/containerd/nerdctl/pkg/platformutil" 33 "github.com/containerd/nerdctl/pkg/referenceutil" 34 "github.com/containerd/stargz-snapshotter/ipfs" 35 ipfsclient "github.com/containerd/stargz-snapshotter/ipfs/client" 36 "github.com/docker/docker/errdefs" 37 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 38 ) 39 40 const ipfsPathEnv = "IPFS_PATH" 41 42 // EnsureImage pull the specified image from IPFS. 43 func EnsureImage(ctx context.Context, client *containerd.Client, stdout, stderr io.Writer, snapshotter string, scheme string, ref string, mode imgutil.PullMode, ocispecPlatforms []ocispec.Platform, unpack *bool, quiet bool, ipfsPath string, rFlags imgutil.RemoteSnapshotterFlags) (*imgutil.EnsuredImage, error) { 44 switch mode { 45 case "always", "missing", "never": 46 // NOP 47 default: 48 return nil, fmt.Errorf("unexpected pull mode: %q", mode) 49 } 50 switch scheme { 51 case "ipfs", "ipns": 52 // NOP 53 default: 54 return nil, fmt.Errorf("unexpected scheme: %q", scheme) 55 } 56 57 // if not `always` pull and given one platform and image found locally, return existing image directly. 58 if mode != "always" && len(ocispecPlatforms) == 1 { 59 if res, err := imgutil.GetExistingImage(ctx, client, snapshotter, ref, ocispecPlatforms[0]); err == nil { 60 return res, nil 61 } else if !errdefs.IsNotFound(err) { 62 return nil, err 63 } 64 } 65 66 if mode == "never" { 67 return nil, fmt.Errorf("image %q is not available", ref) 68 } 69 r, err := ipfs.NewResolver(ipfs.ResolverOptions{ 70 Scheme: scheme, 71 IPFSPath: lookupIPFSPath(ipfsPath), 72 }) 73 if err != nil { 74 return nil, err 75 } 76 return imgutil.PullImage(ctx, client, stdout, stderr, snapshotter, r, ref, ocispecPlatforms, unpack, quiet, rFlags) 77 } 78 79 // Push pushes the specified image to IPFS. 80 func Push(ctx context.Context, client *containerd.Client, rawRef string, layerConvert converter.ConvertFunc, allPlatforms bool, platform []string, ensureImage bool, ipfsPath string) (string, error) { 81 platMC, err := platformutil.NewMatchComparer(allPlatforms, platform) 82 if err != nil { 83 return "", err 84 } 85 ipath := lookupIPFSPath(ipfsPath) 86 if ensureImage { 87 // Ensure image contents are fully downloaded 88 log.G(ctx).Infof("ensuring image contents") 89 if err := ensureContentsOfIPFSImage(ctx, client, rawRef, allPlatforms, platform, ipath); err != nil { 90 log.G(ctx).WithError(err).Warnf("failed to ensure the existence of image %q", rawRef) 91 } 92 } 93 ref, err := referenceutil.ParseAny(rawRef) 94 if err != nil { 95 return "", err 96 } 97 return ipfs.PushWithIPFSPath(ctx, client, ref.String(), layerConvert, platMC, &ipath) 98 } 99 100 // ensureContentsOfIPFSImage ensures that the entire contents of an existing IPFS image are fully downloaded to containerd. 101 func ensureContentsOfIPFSImage(ctx context.Context, client *containerd.Client, ref string, allPlatforms bool, platform []string, ipfsPath string) error { 102 iurl, err := ipfsclient.GetIPFSAPIAddress(ipfsPath, "http") 103 if err != nil { 104 return err 105 } 106 platMC, err := platformutil.NewMatchComparer(allPlatforms, platform) 107 if err != nil { 108 return err 109 } 110 var img images.Image 111 walker := &imagewalker.ImageWalker{ 112 Client: client, 113 OnFound: func(ctx context.Context, found imagewalker.Found) error { 114 img = found.Image 115 return nil 116 }, 117 } 118 n, err := walker.Walk(ctx, ref) 119 if err != nil { 120 return err 121 } else if n == 0 { 122 return fmt.Errorf("image does not exist: %q", ref) 123 } else if n > 1 { 124 return fmt.Errorf("ambiguous reference %q matched %d objects", ref, n) 125 } 126 cs := client.ContentStore() 127 childrenHandler := images.ChildrenHandler(cs) 128 childrenHandler = images.SetChildrenLabels(cs, childrenHandler) 129 childrenHandler = images.FilterPlatforms(childrenHandler, platMC) 130 return images.Dispatch(ctx, images.Handlers( 131 remotes.FetchHandler(cs, &fetcher{ipfsclient.New(iurl)}), 132 childrenHandler, 133 ), nil, img.Target) 134 } 135 136 // fetcher fetches a file from IPFS 137 // TODO: fix github.com/containerd/stargz-snapshotter/ipfs to export this and we should import that 138 type fetcher struct { 139 ipfsclient *ipfsclient.Client 140 } 141 142 func (f *fetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) { 143 cid, err := ipfs.GetCID(desc) 144 if err != nil { 145 return nil, err 146 } 147 off, size := 0, int(desc.Size) 148 return f.ipfsclient.Get("/ipfs/"+cid, &off, &size) 149 } 150 151 // If IPFS_PATH is specified, this will be used. 152 // If not, "~/.ipfs" will be used. 153 // The behaviour is compatible to kubo: https://github.com/ipfs/go-ipfs-http-client/blob/171fcd55e3b743c38fb9d78a34a3a703ee0b5e89/api.go#L43-L44 154 // Optionally takes ipfsPath string having the highest priority. 155 func lookupIPFSPath(ipfsPath string) string { 156 var ipath string 157 if idir := os.Getenv(ipfsPathEnv); idir != "" { 158 ipath = idir 159 } 160 if ipath == "" { 161 ipath = "~/.ipfs" 162 } 163 if ipfsPath != "" { 164 ipath = ipfsPath 165 } 166 return ipath 167 }