Skip to content

Commit 08a5586

Browse files
committed
merge branch 'store-unpack-arguments'
Implements #14 Signed-off-by: Aleksa Sarai <[email protected]>
2 parents ecc86f9 + e2002fc commit 08a5586

File tree

9 files changed

+144
-108
lines changed

9 files changed

+144
-108
lines changed

cmd/umoci/repack.go

Lines changed: 22 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
"github.com/cyphar/umoci/image/cas"
3131
"github.com/cyphar/umoci/image/generator"
3232
"github.com/cyphar/umoci/image/layer"
33-
"github.com/cyphar/umoci/pkg/idtools"
3433
"github.com/opencontainers/image-spec/specs-go/v1"
3534
"github.com/urfave/cli"
3635
"github.com/vbatts/go-mtree"
@@ -40,11 +39,10 @@ import (
4039
var repackCommand = cli.Command{
4140
Name: "repack",
4241
Usage: "repacks an OCI runtime bundle into a reference",
43-
ArgsUsage: `--image <image-path> --from <reference> --bundle <bundle-path>
42+
ArgsUsage: `--image <image-path> --bundle <bundle-path>
4443
45-
Where "<image-path>" is the path to the OCI image, "<reference>" is the name of
46-
the reference descriptor which was used to generate the original runtime bundle
47-
and "<bundle-path>" is the destination to repack the image to.
44+
Where "<image-path>" is the path to the OCI image, and "<bundle-path>" is the
45+
bundle from which to generate the required layers.
4846
4947
It should be noted that this is not the same as oci-create-layer because it
5048
uses go-mtree to create diff layers from runtime bundles unpacked with
@@ -57,10 +55,6 @@ manifest and configuration information uses the new diff atop the old manifest.`
5755
Name: "image",
5856
Usage: "path to OCI image bundle",
5957
},
60-
cli.StringFlag{
61-
Name: "from",
62-
Usage: "reference descriptor name to repack",
63-
},
6458
cli.StringFlag{
6559
Name: "bundle",
6660
Usage: "destination bundle path",
@@ -69,18 +63,6 @@ manifest and configuration information uses the new diff atop the old manifest.`
6963
Name: "tag",
7064
Usage: "tag name for repacked image",
7165
},
72-
cli.StringSliceFlag{
73-
Name: "uid-map",
74-
Usage: "specifies a uid mapping to use when repacking",
75-
},
76-
cli.StringSliceFlag{
77-
Name: "gid-map",
78-
Usage: "specifies a gid mapping to use when repacking",
79-
},
80-
cli.BoolFlag{
81-
Name: "rootless",
82-
Usage: "enable rootless unpacking support",
83-
},
8466
},
8567

8668
Action: repack,
@@ -96,48 +78,23 @@ func repack(ctx *cli.Context) error {
9678
if bundlePath == "" {
9779
return fmt.Errorf("bundle path cannot be empty")
9880
}
99-
fromName := ctx.String("from")
100-
if fromName == "" {
101-
return fmt.Errorf("reference name cannot be empty")
102-
}
10381

104-
// Parse map options.
105-
mapOptions := layer.MapOptions{
106-
Rootless: ctx.Bool("rootless"),
107-
}
108-
// We need to set mappings if we're in rootless mode.
109-
if mapOptions.Rootless {
110-
if !ctx.IsSet("uid-map") {
111-
ctx.Set("uid-map", fmt.Sprintf("%d:0:1", os.Geteuid()))
112-
logrus.WithFields(logrus.Fields{
113-
"map.uid": ctx.StringSlice("uid-map"),
114-
}).Info("setting default rootless --uid-map option")
115-
}
116-
if !ctx.IsSet("gid-map") {
117-
ctx.Set("gid-map", fmt.Sprintf("%d:0:1", os.Getegid()))
118-
logrus.WithFields(logrus.Fields{
119-
"map.gid": ctx.StringSlice("gid-map"),
120-
}).Info("setting default rootless --gid-map option")
121-
}
122-
}
123-
for _, uidmap := range ctx.StringSlice("uid-map") {
124-
idMap, err := idtools.ParseMapping(uidmap)
125-
if err != nil {
126-
return fmt.Errorf("failure parsing --uid-map %s: %s", uidmap, err)
127-
}
128-
mapOptions.UIDMappings = append(mapOptions.UIDMappings, idMap)
129-
}
130-
for _, gidmap := range ctx.StringSlice("gid-map") {
131-
idMap, err := idtools.ParseMapping(gidmap)
132-
if err != nil {
133-
return fmt.Errorf("failure parsing --gid-map %s: %s", gidmap, err)
134-
}
135-
mapOptions.GIDMappings = append(mapOptions.GIDMappings, idMap)
82+
// Read the metadata first.
83+
meta, err := ReadBundleMeta(bundlePath)
84+
if err != nil {
85+
return fmt.Errorf("error reading umoci.json metadata: %s", err)
13686
}
87+
13788
logrus.WithFields(logrus.Fields{
138-
"map.uid": mapOptions.UIDMappings,
139-
"map.gid": mapOptions.GIDMappings,
140-
}).Infof("parsed mappings")
89+
"version": meta.Version,
90+
"from": meta.From,
91+
"map_options": meta.MapOptions,
92+
}).Debugf("umoci: loaded UmociMeta metadata")
93+
94+
// FIXME: Implement support for manifest lists.
95+
if meta.From.MediaType != v1.MediaTypeImageManifest {
96+
return fmt.Errorf("--from descriptor does not point to v1.MediaTypeImageManifest: not implemented: %s", meta.From.MediaType)
97+
}
14198

14299
// Get a reference to the CAS.
143100
engine, err := cas.Open(imagePath)
@@ -146,24 +103,13 @@ func repack(ctx *cli.Context) error {
146103
}
147104
defer engine.Close()
148105

149-
fromDescriptor, err := engine.GetReference(context.TODO(), fromName)
150-
if err != nil {
151-
return err
152-
}
153-
154-
// FIXME: Implement support for manifest lists.
155-
if fromDescriptor.MediaType != v1.MediaTypeImageManifest {
156-
return fmt.Errorf("--from descriptor does not point to v1.MediaTypeImageManifest: not implemented: %s", fromDescriptor.MediaType)
157-
}
158-
159-
mtreeName := strings.Replace(fromDescriptor.Digest, "sha256:", "sha256_", 1)
106+
mtreeName := strings.Replace(meta.From.Digest, "sha256:", "sha256_", 1)
160107
mtreePath := filepath.Join(bundlePath, mtreeName+".mtree")
161108
fullRootfsPath := filepath.Join(bundlePath, layer.RootfsName)
162109

163110
logrus.WithFields(logrus.Fields{
164111
"image": imagePath,
165112
"bundle": bundlePath,
166-
"ref": fromName,
167113
"rootfs": layer.RootfsName,
168114
"mtree": mtreePath,
169115
}).Debugf("umoci: repacking OCI image")
@@ -185,7 +131,7 @@ func repack(ctx *cli.Context) error {
185131
"keywords": keywords,
186132
}).Debugf("umoci: parsed mtree spec")
187133

188-
diffs, err := mtree.Check(fullRootfsPath, spec, keywords, mapOptions.Rootless)
134+
diffs, err := mtree.Check(fullRootfsPath, spec, keywords, meta.MapOptions.Rootless)
189135
if err != nil {
190136
return err
191137
}
@@ -194,7 +140,7 @@ func repack(ctx *cli.Context) error {
194140
"ndiff": len(diffs),
195141
}).Debugf("umoci: checked mtree spec")
196142

197-
reader, err := layer.GenerateLayer(fullRootfsPath, diffs, &mapOptions)
143+
reader, err := layer.GenerateLayer(fullRootfsPath, diffs, &meta.MapOptions)
198144
if err != nil {
199145
return err
200146
}
@@ -250,7 +196,7 @@ func repack(ctx *cli.Context) error {
250196
"size": layerSize,
251197
}).Debugf("umoci: generated new diff layer")
252198

253-
manifestBlob, err := cas.FromDescriptor(context.TODO(), engine, fromDescriptor)
199+
manifestBlob, err := cas.FromDescriptor(context.TODO(), engine, &meta.From)
254200
if err != nil {
255201
return err
256202
}
@@ -331,6 +277,7 @@ func repack(ctx *cli.Context) error {
331277
"size": newDescriptor.Size,
332278
}).Infof("created new image")
333279

280+
// FIXME: This should be mandatory.
334281
tagName := ctx.String("tag")
335282
if tagName == "" {
336283
return nil

cmd/umoci/unpack.go

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,13 @@ func unpack(ctx *cli.Context) error {
111111
return fmt.Errorf("reference name cannot be empty")
112112
}
113113

114+
var meta UmociMeta
115+
meta.Version = ctx.App.Version
116+
114117
// Parse map options.
115-
mapOptions := layer.MapOptions{
116-
Rootless: ctx.Bool("rootless"),
117-
}
118118
// We need to set mappings if we're in rootless mode.
119-
if mapOptions.Rootless {
119+
meta.MapOptions.Rootless = ctx.Bool("rootless")
120+
if meta.MapOptions.Rootless {
120121
if !ctx.IsSet("uid-map") {
121122
ctx.Set("uid-map", fmt.Sprintf("%d:0:1", os.Geteuid()))
122123
logrus.WithFields(logrus.Fields{
@@ -136,18 +137,18 @@ func unpack(ctx *cli.Context) error {
136137
if err != nil {
137138
return fmt.Errorf("failure parsing --uid-map %s: %s", uidmap, err)
138139
}
139-
mapOptions.UIDMappings = append(mapOptions.UIDMappings, idMap)
140+
meta.MapOptions.UIDMappings = append(meta.MapOptions.UIDMappings, idMap)
140141
}
141142
for _, gidmap := range ctx.StringSlice("gid-map") {
142143
idMap, err := idtools.ParseMapping(gidmap)
143144
if err != nil {
144145
return fmt.Errorf("failure parsing --gid-map %s: %s", gidmap, err)
145146
}
146-
mapOptions.GIDMappings = append(mapOptions.GIDMappings, idMap)
147+
meta.MapOptions.GIDMappings = append(meta.MapOptions.GIDMappings, idMap)
147148
}
148149
logrus.WithFields(logrus.Fields{
149-
"map.uid": mapOptions.UIDMappings,
150-
"map.gid": mapOptions.GIDMappings,
150+
"map.uid": meta.MapOptions.UIDMappings,
151+
"map.gid": meta.MapOptions.GIDMappings,
151152
}).Infof("parsed mappings")
152153

153154
// Get a reference to the CAS.
@@ -161,18 +162,20 @@ func unpack(ctx *cli.Context) error {
161162
if err != nil {
162163
return err
163164
}
164-
manifestBlob, err := cas.FromDescriptor(context.TODO(), engine, fromDescriptor)
165+
meta.From = *fromDescriptor
166+
167+
manifestBlob, err := cas.FromDescriptor(context.TODO(), engine, &meta.From)
165168
if err != nil {
166169
return err
167170
}
168171
defer manifestBlob.Close()
169172

170173
// FIXME: Implement support for manifest lists.
171174
if manifestBlob.MediaType != v1.MediaTypeImageManifest {
172-
return fmt.Errorf("--from descriptor does not point to v1.MediaTypeImageManifest: not implemented: %s", fromDescriptor.MediaType)
175+
return fmt.Errorf("--from descriptor does not point to v1.MediaTypeImageManifest: not implemented: %s", meta.From.MediaType)
173176
}
174177

175-
mtreeName := strings.Replace(fromDescriptor.Digest, "sha256:", "sha256_", 1)
178+
mtreeName := strings.Replace(meta.From.Digest, "sha256:", "sha256_", 1)
176179
mtreePath := filepath.Join(bundlePath, mtreeName+".mtree")
177180
fullRootfsPath := filepath.Join(bundlePath, layer.RootfsName)
178181

@@ -188,7 +191,7 @@ func unpack(ctx *cli.Context) error {
188191

189192
// Unpack the runtime bundle.
190193
if err := os.MkdirAll(bundlePath, 0755); err != nil {
191-
return fmt.Errorf("failed to create bundle path: %q", err)
194+
return fmt.Errorf("failed to create bundle path: %s", err)
192195
}
193196
// XXX: We should probably defer os.RemoveAll(bundlePath).
194197

@@ -205,8 +208,8 @@ func unpack(ctx *cli.Context) error {
205208
// extraction). I'm considering reimplementing it just so there are
206209
// competing implementations of this extraction functionality.
207210
// https://github.com/opencontainers/image-tools/issues/74
208-
if err := layer.UnpackManifest(context.TODO(), engine, bundlePath, *manifest, &mapOptions); err != nil {
209-
return fmt.Errorf("failed to create runtime bundle: %q", err)
211+
if err := layer.UnpackManifest(context.TODO(), engine, bundlePath, *manifest, &meta.MapOptions); err != nil {
212+
return fmt.Errorf("failed to create runtime bundle: %s", err)
210213
}
211214

212215
// Create the mtree manifest.
@@ -223,21 +226,31 @@ func unpack(ctx *cli.Context) error {
223226
"mtree": mtreePath,
224227
}).Debugf("umoci: generating mtree manifest")
225228

226-
dh, err := mtree.Walk(fullRootfsPath, nil, keywords, mapOptions.Rootless)
229+
dh, err := mtree.Walk(fullRootfsPath, nil, keywords, meta.MapOptions.Rootless)
227230
if err != nil {
228-
return fmt.Errorf("failed to generate mtree spec: %q", err)
231+
return fmt.Errorf("failed to generate mtree spec: %s", err)
229232
}
230233

231234
fh, err := os.OpenFile(mtreePath, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0644)
232235
if err != nil {
233-
return fmt.Errorf("failed to open mtree spec for writing: %q", err)
236+
return fmt.Errorf("failed to open mtree spec for writing: %s", err)
234237
}
235238
defer fh.Close()
236239

237240
logrus.Debugf("umoci: saving mtree manifest")
238241

239242
if _, err := dh.WriteTo(fh); err != nil {
240-
return fmt.Errorf("failed to write mtree spec: %q", err)
243+
return fmt.Errorf("failed to write mtree spec: %s", err)
244+
}
245+
246+
logrus.WithFields(logrus.Fields{
247+
"version": meta.Version,
248+
"from": meta.From,
249+
"map_options": meta.MapOptions,
250+
}).Debugf("umoci: saving UmociMeta metadata")
251+
252+
if err := WriteBundleMeta(bundlePath, meta); err != nil {
253+
return fmt.Errorf("failed to write umoci.json metadata: %s", err)
241254
}
242255

243256
logrus.Debugf("umoci: unpacking complete")

cmd/umoci/utils.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/cyphar/umoci/image/layer"
11+
ispec "github.com/opencontainers/image-spec/specs-go/v1"
12+
)
13+
14+
// FIXME: This should be moved to a library. Too much of this code is in the
15+
// cmd/... code, but should really be refactored to the point where it
16+
// can be useful to other people. This is _particularly_ true for the
17+
// code which repacks images (the changes to the config, manifest and
18+
// CAS should be made into a library).
19+
20+
// UmociMetaName is the name of umoci's metadata file that is stored in all
21+
// bundles extracted by umoci.
22+
const UmociMetaName = "umoci.json"
23+
24+
// UmociMeta represents metadata about how umoci unpacked an image to a bundle
25+
// and other similar information. It is used to keep track of information that
26+
// is required when repacking an image and other similar bundle information.
27+
type UmociMeta struct {
28+
// Version is the version of umoci used to unpack the bundle. This is used
29+
// to future-proof the umoci.json information.
30+
Version string `json:"umoci_version"`
31+
32+
// From is a copy of the descriptor pointing to the image manifest that was
33+
// used to unpack the bundle. Essentially it's a resolved form of the
34+
// --from argument to umoci-unpack(1).
35+
From ispec.Descriptor `json:"from_descriptor"`
36+
37+
// MapOptions is the parsed version of --uid-map, --gid-map and --rootless
38+
// arguments to umoci-unpack(1). While all of these options technically do
39+
// not need to be the same for corresponding umoci-unpack(1) and
40+
// umoci-repack(1) calls, changing them is not recommended and so the
41+
// default should be that they are the same.
42+
MapOptions layer.MapOptions `json:"map_options"`
43+
}
44+
45+
// WriteTo writes a JSON-serialised version of UmociMeta to the given io.Writer.
46+
func (m UmociMeta) WriteTo(w io.Writer) (int64, error) {
47+
buf := new(bytes.Buffer)
48+
err := json.NewEncoder(io.MultiWriter(buf, w)).Encode(m)
49+
return int64(buf.Len()), err
50+
}
51+
52+
// WriteBundleMeta writes an umoci.json file to the given bundle path.
53+
func WriteBundleMeta(bundle string, meta UmociMeta) error {
54+
fh, err := os.Create(filepath.Join(bundle, UmociMetaName))
55+
if err != nil {
56+
return err
57+
}
58+
defer fh.Close()
59+
60+
_, err = meta.WriteTo(fh)
61+
return err
62+
}
63+
64+
// ReadBundleMeta reads and parses the umoci.json file from a given bundle path.
65+
func ReadBundleMeta(bundle string) (UmociMeta, error) {
66+
var meta UmociMeta
67+
68+
fh, err := os.Open(filepath.Join(bundle, UmociMetaName))
69+
if err != nil {
70+
return meta, err
71+
}
72+
defer fh.Close()
73+
74+
err = json.NewDecoder(fh).Decode(&meta)
75+
return meta, err
76+
}

image/layer/utils.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ import (
3131
type MapOptions struct {
3232
// UIDMappings and GIDMappings are the UID and GID mappings to apply when
3333
// packing and unpacking image rootfs layers.
34-
UIDMappings []rspec.IDMapping
35-
GIDMappings []rspec.IDMapping
34+
UIDMappings []rspec.IDMapping `json:"uid_mappings"`
35+
GIDMappings []rspec.IDMapping `json:"gid_mappings"`
3636

3737
// Rootless specifies whether any to error out if chown fails.
38-
Rootless bool
38+
Rootless bool `json:"rootless"`
3939
}
4040

4141
// mapHeader maps a tar.Header generated from the filesystem so that it

0 commit comments

Comments
 (0)