github.com/mattmoor/mink@v1.3.1/pkg/bundles/git/bundle.go (about) 1 /* 2 Copyright 2020 The Knative 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 git 18 19 import ( 20 "archive/tar" 21 "bytes" 22 "context" 23 "errors" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 29 "github.com/google/go-containerregistry/pkg/name" 30 v1 "github.com/google/go-containerregistry/pkg/v1" 31 "github.com/google/go-containerregistry/pkg/v1/mutate" 32 "github.com/google/go-containerregistry/pkg/v1/tarball" 33 "gopkg.in/src-d/go-billy.v4/memfs" 34 "gopkg.in/src-d/go-git.v4" 35 "gopkg.in/src-d/go-git.v4/plumbing" 36 "gopkg.in/src-d/go-git.v4/plumbing/object" 37 "gopkg.in/src-d/go-git.v4/storage/memory" 38 39 "github.com/mattmoor/mink/pkg/bundles" 40 "github.com/mattmoor/mink/pkg/bundles/kontext" 41 ) 42 43 func bundle(ctx context.Context, opts Options) (v1.Layer, error) { 44 // Filesystem abstraction based on memory 45 fs := memfs.New() 46 // Git objects storer based on memory 47 storer := memory.NewStorage() 48 49 // Clones the repository into the worktree (fs) and storer all the .git 50 // content into the storer 51 repo, err := git.CloneContext(ctx, storer, fs, &git.CloneOptions{ 52 URL: opts.URL, 53 ReferenceName: opts.Ref, 54 SingleBranch: true, 55 }) 56 if err != nil { 57 return nil, err 58 } 59 ref, err := repo.Head() 60 if err != nil { 61 return nil, err 62 } 63 commit, err := repo.CommitObject(ref.Hash()) 64 if err != nil { 65 return nil, err 66 } 67 tree, err := commit.Tree() 68 if err != nil { 69 return nil, err 70 } 71 buf := bytes.NewBuffer(nil) 72 tw := tar.NewWriter(buf) 73 defer tw.Close() 74 75 // Add an entry for the root kontext directory 76 // This is to facilitate testing for compatibility with kontext. 77 if err := tw.WriteHeader(&tar.Header{ 78 Name: kontext.StoragePath, 79 Typeflag: tar.TypeDir, 80 Mode: 0555, 81 }); err != nil { 82 return nil, err 83 } 84 85 w := object.NewTreeWalker(tree, true /* recursive */, make(map[plumbing.Hash]bool)) 86 defer w.Close() 87 for { 88 name, _, err := w.Next() 89 if errors.Is(err, io.EOF) { 90 break 91 } else if err != nil { 92 return nil, err 93 } 94 95 if err := func() error { 96 // Do not chase symlinks. 97 info, err := fs.Lstat(name) 98 if err != nil { 99 return err 100 } 101 102 newPath := filepath.Join(kontext.StoragePath, name) 103 104 // Handle directories 105 if info.Mode().IsDir() { 106 return tw.WriteHeader(&tar.Header{ 107 Name: newPath, 108 Typeflag: tar.TypeDir, 109 Mode: 0555, 110 }) 111 } 112 113 // Handle symlinks 114 if info.Mode()&os.ModeSymlink != 0 { 115 linkname, err := fs.Readlink(name) 116 if err != nil { 117 return err 118 } 119 return tw.WriteHeader(&tar.Header{ 120 Name: newPath, 121 Typeflag: tar.TypeSymlink, 122 Mode: 0555, 123 Linkname: linkname, 124 }) 125 } 126 127 // Open the file to copy it into the tarball. 128 file, err := fs.Open(name) 129 if err != nil { 130 return err 131 } 132 defer file.Close() 133 134 // Copy the file into the image tarball. 135 if err := tw.WriteHeader(&tar.Header{ 136 Name: newPath, 137 Size: info.Size(), 138 Typeflag: tar.TypeReg, 139 // Use a fixed Mode, so that this isn't sensitive to the directory and umask 140 // under which it was created. Additionally, windows can only set 0222, 141 // 0444, or 0666, none of which are executable. 142 Mode: 0555, 143 }); err != nil { 144 return err 145 } 146 _, err = io.Copy(tw, file) 147 return err 148 }(); err != nil { 149 return nil, fmt.Errorf("error processing %q: %w", name, err) 150 } 151 } 152 153 return tarball.LayerFromReader(bytes.NewBuffer(buf.Bytes())) 154 } 155 156 // Options contains a collection of options for configuring how things are bundled from git. 157 type Options struct { 158 // URL contains the url from which to clone the git repository. 159 URL string 160 // Ref contains the ref to check out for bundling. 161 Ref plumbing.ReferenceName 162 } 163 164 // Bundle packages up the given git repo as a self-extracting container image based 165 // on BaseImage and publishes it to tag. 166 func Bundle(ctx context.Context, opts Options, tag name.Tag) (name.Digest, error) { 167 layer, err := bundle(ctx, opts) 168 if err != nil { 169 return name.Digest{}, err 170 } 171 172 return bundles.Map(ctx, kontext.BaseImage, tag, func(ctx context.Context, img v1.Image) (v1.Image, error) { 173 return mutate.AppendLayers(img, layer) 174 }) 175 }