-
Notifications
You must be signed in to change notification settings - Fork 766
/
add.go
134 lines (115 loc) · 3.81 KB
/
add.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package source
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containers/image/v5/types"
"github.com/containers/storage/pkg/archive"
specV1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// AddOptions include data to alter certain knobs when adding a source artifact
// to a source image.
type AddOptions struct {
// Annotations for the source artifact.
Annotations []string
}
// annotations parses the specified annotations and transforms them into a map.
// A given annotation can be specified only once.
func (o *AddOptions) annotations() (map[string]string, error) {
annotations := make(map[string]string)
for _, unparsed := range o.Annotations {
parsed := strings.SplitN(unparsed, "=", 2)
if len(parsed) != 2 {
return nil, errors.Errorf("invalid annotation %q (expected format is \"key=value\")", unparsed)
}
if _, exists := annotations[parsed[0]]; exists {
return nil, errors.Errorf("annotation %q specified more than once", parsed[0])
}
annotations[parsed[0]] = parsed[1]
}
return annotations, nil
}
// Add adds the specified source artifact at `artifactPath` to the source image
// at `sourcePath`. Note that the artifact will be added as a gzip-compressed
// tar ball. Add attempts to auto-tar and auto-compress only if necessary.
func Add(ctx context.Context, sourcePath string, artifactPath string, options AddOptions) error {
// Let's first make sure `sourcePath` exists and that we can access it.
if _, err := os.Stat(sourcePath); err != nil {
return err
}
annotations, err := options.annotations()
if err != nil {
return err
}
ociDest, err := openOrCreateSourceImage(ctx, sourcePath)
if err != nil {
return err
}
defer ociDest.Close()
tarStream, err := archive.TarWithOptions(artifactPath, &archive.TarOptions{Compression: archive.Gzip})
if err != nil {
return errors.Wrap(err, "error creating compressed tar stream")
}
info := types.BlobInfo{
Size: -1, // "unknown": we'll get that information *after* adding
}
addedBlob, err := ociDest.PutBlob(ctx, tarStream, info, nil, false)
if err != nil {
return errors.Wrap(err, "error adding source artifact")
}
// Add the new layers to the source image's manifest.
manifest, oldManifestDigest, _, err := readManifestFromOCIPath(ctx, sourcePath)
if err != nil {
return err
}
manifest.Layers = append(manifest.Layers,
specV1.Descriptor{
MediaType: specV1.MediaTypeImageLayerGzip,
Digest: addedBlob.Digest,
Size: addedBlob.Size,
Annotations: annotations,
},
)
manifestDigest, manifestSize, err := writeManifest(ctx, manifest, ociDest)
if err != nil {
return err
}
// Now, as we've written the updated manifest, we can delete the
// previous one. `types.ImageDestination` doesn't expose a high-level
// API to manage multi-manifest destination, so we need to do it
// manually. Not an issue, since paths are predictable for an OCI
// layout.
if err := removeBlob(oldManifestDigest, sourcePath); err != nil {
return errors.Wrap(err, "error removing old manifest")
}
manifestDescriptor := specV1.Descriptor{
MediaType: specV1.MediaTypeImageManifest,
Digest: *manifestDigest,
Size: manifestSize,
}
if err := updateIndexWithNewManifestDescriptor(&manifestDescriptor, sourcePath); err != nil {
return err
}
return nil
}
func updateIndexWithNewManifestDescriptor(manifest *specV1.Descriptor, sourcePath string) error {
index := specV1.Index{}
indexPath := filepath.Join(sourcePath, "index.json")
rawData, err := ioutil.ReadFile(indexPath)
if err != nil {
return err
}
if err := json.Unmarshal(rawData, &index); err != nil {
return err
}
index.Manifests = []specV1.Descriptor{*manifest}
rawData, err = json.Marshal(&index)
if err != nil {
return err
}
return ioutil.WriteFile(indexPath, rawData, 0644)
}