From e128a276045c5e7b163521651a1e0a1f8992ef33 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Sun, 9 Apr 2017 15:54:15 +1000 Subject: [PATCH 1/7] oci: layer: split out UnpackRuntimeConfig Make UnpackManifest a bit less monolithic, though this code really needs a stream cleaning. This is in preparation of umoci-raw-config(1). Signed-off-by: Aleksa Sarai --- oci/layer/unpack.go | 54 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/oci/layer/unpack.go b/oci/layer/unpack.go index 195622cf0..1011b847a 100644 --- a/oci/layer/unpack.go +++ b/oci/layer/unpack.go @@ -87,11 +87,6 @@ func isLayerType(mediaType string) bool { func UnpackManifest(ctx context.Context, engine cas.Engine, bundle string, manifest ispec.Manifest, opt *MapOptions) error { engineExt := casext.Engine{engine} - var mapOptions MapOptions - if opt != nil { - mapOptions = *opt - } - // Create the bundle directory. We only error out if config.json or rootfs/ // already exists, because we cannot be sure that the user intended us to // extract over an existing bundle. @@ -207,9 +202,54 @@ func UnpackManifest(ctx context.Context, engine cas.Engine, bundle string, manif // Generate a runtime configuration file from ispec.Image. log.Infof("unpack configuration: %s", configBlob.Digest) + configFile, err := os.Create(configPath) + if err != nil { + return errors.Wrap(err, "open config.json") + } + defer configFile.Close() + + if err := UnpackRuntimeJSON(ctx, engine, configFile, rootfsPath, manifest, opt); err != nil { + return errors.Wrap(err, "unpack config.json") + } + return nil +} + +// UnpackRuntimeJSON converts a given manifest's configuration to a runtime +// configuration and writes it to the given writer. If rootfs is specified, it +// is sourced during the configuration generation (for conversion of +// Config.User and other similar jobs -- which will error out if the user could +// not be parsed). If rootfs is not specified (is an empty string) then all +// conversions that require sourcing the rootfs will be set to their default +// values. +// +// XXX: I don't like this API. It has way too many arguments. +func UnpackRuntimeJSON(ctx context.Context, engine cas.Engine, configFile io.Writer, rootfs string, manifest ispec.Manifest, opt *MapOptions) error { + engineExt := casext.Engine{engine} + + var mapOptions MapOptions + if opt != nil { + mapOptions = *opt + } + + // In order to verify the DiffIDs as we extract layers, we have to get the + // .Config blob first. But we can't extract it (generate the runtime + // config) until after we have the full rootfs generated. + configBlob, err := engineExt.FromDescriptor(ctx, manifest.Config) + if err != nil { + return errors.Wrap(err, "get config blob") + } + defer configBlob.Close() + if configBlob.MediaType != ispec.MediaTypeImageConfig { + return errors.Errorf("unpack manifest: config blob is not correct mediatype %s: %s", ispec.MediaTypeImageConfig, configBlob.MediaType) + } + config, ok := configBlob.Data.(ispec.Image) + if !ok { + // Should _never_ be reached. + return errors.Errorf("[internal error] unknown config blob type: %s", configBlob.MediaType) + } g := rgen.New() - if err := iconv.MutateRuntimeSpec(g, rootfsPath, config, manifest); err != nil { + if err := iconv.MutateRuntimeSpec(g, rootfs, config, manifest); err != nil { return errors.Wrap(err, "generate config.json") } @@ -231,7 +271,7 @@ func UnpackManifest(ctx context.Context, engine cas.Engine, bundle string, manif } // Save the config.json. - if err := g.SaveToFile(configPath, rgen.ExportOptions{}); err != nil { + if err := g.Save(configFile, rgen.ExportOptions{}); err != nil { return errors.Wrap(err, "write config.json") } return nil From f7157c650bff5e4fbf3215559b1fe63f502010d4 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Sun, 9 Apr 2017 21:11:39 +1000 Subject: [PATCH 2/7] oci: config: convert: make rootfs="" use defaults If the Config.User string is numeric then this isn't an issue, however we need to return defaults if Config.User is some user and we haven't provided a rootfs. This is going to be required for umoci-raw-config(1) to work nicely. Signed-off-by: Aleksa Sarai --- oci/config/convert/runtime.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/oci/config/convert/runtime.go b/oci/config/convert/runtime.go index 53119f2ee..9cfae1ad0 100644 --- a/oci/config/convert/runtime.go +++ b/oci/config/convert/runtime.go @@ -21,6 +21,7 @@ import ( "path/filepath" "strings" + "github.com/apex/log" "github.com/openSUSE/umoci/third_party/user" ispec "github.com/opencontainers/image-spec/specs-go/v1" rspec "github.com/opencontainers/runtime-spec/specs-go" @@ -59,6 +60,9 @@ func parseEnv(env string) (string, string, error) { // MutateRuntimeSpec mutates a given runtime specification generator with the // image configuration provided. It returns the original generator, and does // not modify any fields directly (to allow for chaining). +// +// XXX: This conversion is not actually defined by the image-spec. There is a +// proposal in-the-works though. https://github.com/opencontainers/image-spec/pull/492 func MutateRuntimeSpec(g rgen.Generator, rootfs string, image ispec.Image, manifest ispec.Manifest) error { if image.OS != "linux" { return errors.Errorf("unsupported OS: %s", image.OS) @@ -108,7 +112,13 @@ func MutateRuntimeSpec(g rgen.Generator, rootfs string, image ispec.Image, manif } execUser, err := user.GetExecUserPath(image.Config.User, nil, passwdPath, groupPath) if err != nil { - return errors.Wrapf(err, "cannot parse user spec: '%s'", image.Config.User) + // We only log an error if were not given a rootfs, and we set execUser + // to the "default" (root:root). + if rootfs != "" { + return errors.Wrapf(err, "cannot parse user spec: '%s'", image.Config.User) + } + log.Warnf("could not parse user spec '%s' without a rootfs -- defaulting to root:root", image.Config.User) + execUser = new(user.ExecUser) } g.SetProcessUID(uint32(execUser.Uid)) From fb1bfeae9017d2d29e462035c16fc94ea7d23fa1 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Sun, 9 Apr 2017 22:41:03 +1000 Subject: [PATCH 3/7] cmd: umoci: add umoci-raw-runtime-config(1) Add a new command to allows users that have to parse the generated config.json often much more efficient (by allowing them to skip the layer unpacking steps). Since the output of this command is not quite obvious unless you know what it's doing, hide it behind a "raw" subcommand that should scare away users that don't know enough about the OCI specs. Signed-off-by: Aleksa Sarai --- cmd/umoci/main.go | 8 +- cmd/umoci/raw-runtime-config.go | 171 ++++++++++++++++++++++++++++++++ cmd/umoci/raw.go | 38 +++++++ cmd/umoci/utils_ux.go | 9 ++ 4 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 cmd/umoci/raw-runtime-config.go create mode 100644 cmd/umoci/raw.go diff --git a/cmd/umoci/main.go b/cmd/umoci/main.go index a6c089179..4950c57e1 100644 --- a/cmd/umoci/main.go +++ b/cmd/umoci/main.go @@ -113,6 +113,7 @@ func main() { tagRemoveCommand, tagListCommand, statCommand, + rawSubcommand, } app.Metadata = map[string]interface{}{} @@ -120,7 +121,7 @@ func main() { // In order to make the uxXyz wrappers not too cumbersome we automatically // add them to images with categories set to categoryImage or // categoryLayout. Monkey patching was never this neat. - for idx, cmd := range app.Commands { + for _, cmd := range flattenCommands(app.Commands) { switch cmd.Category { case categoryImage: oldBefore := cmd.Before @@ -136,7 +137,7 @@ func main() { } return nil } - cmd = uxImage(cmd) + *cmd = uxImage(*cmd) case categoryLayout: oldBefore := cmd.Before cmd.Before = func(ctx *cli.Context) error { @@ -148,9 +149,8 @@ func main() { } return nil } - cmd = uxLayout(cmd) + *cmd = uxLayout(*cmd) } - app.Commands[idx] = cmd } // Actually run umoci. diff --git a/cmd/umoci/raw-runtime-config.go b/cmd/umoci/raw-runtime-config.go new file mode 100644 index 000000000..72328bc5a --- /dev/null +++ b/cmd/umoci/raw-runtime-config.go @@ -0,0 +1,171 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016, 2017 SUSE LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "os" + + "github.com/apex/log" + "github.com/openSUSE/umoci/oci/cas" + "github.com/openSUSE/umoci/oci/casext" + "github.com/openSUSE/umoci/oci/layer" + "github.com/openSUSE/umoci/pkg/idtools" + ispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/urfave/cli" + "golang.org/x/net/context" +) + +var rawConfigCommand = cli.Command{ + Name: "runtime-config", + Aliases: []string{"config"}, + Usage: "generates an OCI runtime configuration for an image", + ArgsUsage: `--image [:] [--rootfs ] + +Where "" is the path to the OCI image, "" is the name of the +tagged image to unpack (if not specified, defaults to "latest"), "" is +a rootfs to use as a supplementary "source of truth" for certain generation +operations and "" is the destination to write the runtime +configuration to. + +Note that the results of this may not agree with umoci-unpack(1) because the +--rootfs flag affects how certain properties are interpreted.`, + + // unpack reads manifest information. + Category: "image", + + Flags: []cli.Flag{ + cli.StringSliceFlag{ + Name: "uid-map", + Usage: "specifies a uid mapping to use when generating config", + }, + cli.StringSliceFlag{ + Name: "gid-map", + Usage: "specifies a gid mapping to use when generating config", + }, + cli.BoolFlag{ + Name: "rootless", + Usage: "generate rootless configuration", + }, + cli.StringFlag{ + Name: "rootfs", + Usage: "path to secondary source of truth (root filesystem)", + }, + }, + + Action: rawConfig, + + Before: func(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return errors.Errorf("invalid number of positional arguments: expected ") + } + if ctx.Args().First() == "" { + return errors.Errorf("config.json path cannot be empty") + } + ctx.App.Metadata["config"] = ctx.Args().First() + return nil + }, +} + +func rawConfig(ctx *cli.Context) error { + imagePath := ctx.App.Metadata["--image-path"].(string) + fromName := ctx.App.Metadata["--image-tag"].(string) + configPath := ctx.App.Metadata["config"].(string) + + var meta UmociMeta + meta.Version = ctx.App.Version + + // Parse map options. + // We need to set mappings if we're in rootless mode. + meta.MapOptions.Rootless = ctx.Bool("rootless") + if meta.MapOptions.Rootless { + if !ctx.IsSet("uid-map") { + ctx.Set("uid-map", fmt.Sprintf("%d:0:1", os.Geteuid())) + } + if !ctx.IsSet("gid-map") { + ctx.Set("gid-map", fmt.Sprintf("%d:0:1", os.Getegid())) + } + } + // Parse and set up the mapping options. + for _, uidmap := range ctx.StringSlice("uid-map") { + idMap, err := idtools.ParseMapping(uidmap) + if err != nil { + return errors.Wrapf(err, "failure parsing --uid-map %s: %s", uidmap) + } + meta.MapOptions.UIDMappings = append(meta.MapOptions.UIDMappings, idMap) + } + for _, gidmap := range ctx.StringSlice("gid-map") { + idMap, err := idtools.ParseMapping(gidmap) + if err != nil { + return errors.Wrapf(err, "failure parsing --gid-map %s: %s", gidmap) + } + meta.MapOptions.GIDMappings = append(meta.MapOptions.GIDMappings, idMap) + } + + log.WithFields(log.Fields{ + "map.uid": meta.MapOptions.UIDMappings, + "map.gid": meta.MapOptions.GIDMappings, + }).Debugf("parsed mappings") + + // Get a reference to the CAS. + engine, err := cas.Open(imagePath) + if err != nil { + return errors.Wrap(err, "open CAS") + } + engineExt := casext.Engine{engine} + defer engine.Close() + + fromDescriptor, err := engineExt.GetReference(context.Background(), fromName) + if err != nil { + return errors.Wrap(err, "get descriptor") + } + meta.From = fromDescriptor + + manifestBlob, err := engineExt.FromDescriptor(context.Background(), meta.From) + if err != nil { + return errors.Wrap(err, "get manifest") + } + defer manifestBlob.Close() + + // FIXME: Implement support for manifest lists. + if manifestBlob.MediaType != ispec.MediaTypeImageManifest { + return errors.Wrap(fmt.Errorf("descriptor does not point to ispec.MediaTypeImageManifest: not implemented: %s", meta.From.MediaType), "invalid --image tag") + } + + // Get the manifest. + manifest, ok := manifestBlob.Data.(ispec.Manifest) + if !ok { + // Should _never_ be reached. + return errors.Errorf("[internal error] unknown manifest blob type: %s", manifestBlob.MediaType) + } + + // Generate the configuration. + configFile, err := os.Create(configPath) + if err != nil { + return errors.Wrap(err, "opening config path") + } + defer configFile.Close() + + // Write out the generated config. + log.Info("generating config.json") + if err := layer.UnpackRuntimeJSON(context.Background(), engineExt, configFile, ctx.String("rootfs"), manifest, &meta.MapOptions); err != nil { + return errors.Wrap(err, "generate config") + } + return nil +} diff --git a/cmd/umoci/raw.go b/cmd/umoci/raw.go new file mode 100644 index 000000000..635d84492 --- /dev/null +++ b/cmd/umoci/raw.go @@ -0,0 +1,38 @@ +/* + * umoci: Umoci Modifies Open Containers' Images + * Copyright (C) 2016, 2017 SUSE LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "github.com/urfave/cli" +) + +var rawSubcommand = cli.Command{ + Name: "raw", + Usage: "advanced internal image tooling", + ArgsUsage: `raw [...] + +In order to facilitate more advanced uses of umoci, the umoci-raw(1) +subcommands allow for more fine-grained information to be provided from umoci. +Please do not use these commands if you are not familiar with the intricacies +of the OCI specifications. The top-level umoci-unpack(1) and similar commands +should be sufficient for most use-cases.`, + + Subcommands: []cli.Command{ + rawConfigCommand, + }, +} diff --git a/cmd/umoci/utils_ux.go b/cmd/umoci/utils_ux.go index d731bffb9..f46244737 100644 --- a/cmd/umoci/utils_ux.go +++ b/cmd/umoci/utils_ux.go @@ -29,6 +29,15 @@ import ( // refRegexp defines the regexp that a given OCI tag must obey. var refRegexp = regexp.MustCompile(`^([A-Za-z0-9._-]+)+$`) +func flattenCommands(cmds []cli.Command) []*cli.Command { + var flatten []*cli.Command + for idx, cmd := range cmds { + flatten = append(flatten, &cmds[idx]) + flatten = append(flatten, flattenCommands(cmd.Subcommands)...) + } + return flatten +} + // uxHistory adds the full set of --history.* flags to the given cli.Command as // well as adding relevant validation logic to the .Before of the command. The // values will be stored in ctx.Metadata with the keys "--history.author", From 2d8b4380befaf83e6fe3475f3b87223f7b0c0b0d Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 10 Apr 2017 14:02:04 +1000 Subject: [PATCH 4/7] test: add raw runtime-config tests These tests are basically a clone of the umoci-config(1) tests, but using just umoci-raw-runtime-config(1) to generate the config.json. Signed-off-by: Aleksa Sarai --- test/help.bats | 24 ++ test/raw-config.bats | 662 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 686 insertions(+) create mode 100644 test/raw-config.bats diff --git a/test/help.bats b/test/help.bats index eec7a9c8c..3c088377f 100644 --- a/test/help.bats +++ b/test/help.bats @@ -90,6 +90,30 @@ load helpers [ "$status" -eq 0 ] [[ "${lines[1]}" =~ "umoci tag"+ ]] + umoci raw --help + [ "$status" -eq 0 ] + [[ "${lines[1]}" =~ "umoci raw"+ ]] + + umoci raw -h + [ "$status" -eq 0 ] + [[ "${lines[1]}" =~ "umoci raw"+ ]] + + umoci raw runtime-config --help + [ "$status" -eq 0 ] + [[ "${lines[1]}" =~ "umoci raw runtime-config"+ ]] + + umoci raw runtime-config -h + [ "$status" -eq 0 ] + [[ "${lines[1]}" =~ "umoci raw runtime-config"+ ]] + + umoci raw config --help + [ "$status" -eq 0 ] + [[ "${lines[1]}" =~ "umoci raw runtime-config"+ ]] + + umoci raw config -h + [ "$status" -eq 0 ] + [[ "${lines[1]}" =~ "umoci raw runtime-config"+ ]] + umoci remove --help [ "$status" -eq 0 ] [[ "${lines[1]}" =~ "umoci remove"+ ]] diff --git a/test/raw-config.bats b/test/raw-config.bats new file mode 100644 index 000000000..df963b3f9 --- /dev/null +++ b/test/raw-config.bats @@ -0,0 +1,662 @@ +#!/usr/bin/env bats -t +# umoci: Umoci Modifies Open Containers' Images +# Copyright (C) 2016, 2017 SUSE LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load helpers + +function setup() { + setup_image +} + +function teardown() { + teardown_tmpdirs + teardown_image +} + +@test "umoci raw runtime-config" { + BUNDLE_A="$(setup_tmpdir)" + BUNDLE_B="$(setup_tmpdir)" + + image-verify "${IMAGE}" + + # Unpack the image. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE_A/config.json" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE_A" + + # We need to make sure the config exists. + [ -f "$BUNDLE_A/config.json" ] + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE_B" + + # Make sure that the config was unchanged. + # First clean the config. + jq -SM '.' "$BUNDLE_A/config.json" >"$BATS_TMPDIR/a-config.json" + jq -SM '.' "$BUNDLE_B/config.json" >"$BATS_TMPDIR/b-config.json" + sane_run diff -u "$BATS_TMPDIR/a-config.json" "$BATS_TMPDIR/b-config.json" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "umoci raw runtime-config [missing args]" { + umoci config + [ "$status" -ne 0 ] +} + +@test "umoci raw runtime-config --config.user 'user'" { + BUNDLE_A="$(setup_tmpdir)" + BUNDLE_B="$(setup_tmpdir)" + + image-verify "${IMAGE}" + + # Unpack the image. + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE_A" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE_A" + + # Modify /etc/passwd and /etc/group. + echo "testuser:x:1337:8888:test user:/my home dir :/bin/sh" >> "$BUNDLE_A/rootfs/etc/passwd" + echo "testgroup:x:2581:root,testuser" >> "$BUNDLE_A/rootfs/etc/group" + echo "group:x:9001:testuser" >> "$BUNDLE_A/rootfs/etc/group" + + # Modify the user. + umoci config --image "${IMAGE}:${TAG}" --config.user="testuser" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Generate config.json. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + + # We *don't* want the users to be set because we don't have a rootfs. + + sane_run jq -SM '.process.user.uid' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 0 ] + sane_run jq -SM '.process.user.gid' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 0 ] + sane_run jq -SMr '.process.user.additionalGids' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "null" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.user 'user:group'" { + BUNDLE_A="$(setup_tmpdir)" + BUNDLE_B="$(setup_tmpdir)" + + image-verify "${IMAGE}" + + # Unpack the image. + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE_A" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE_A" + + # Modify /etc/passwd and /etc/group. + echo "testuser:x:1337:8888:test user:/my home dir :/bin/sh" >> "$BUNDLE_A/rootfs/etc/passwd" + echo "testgroup:x:2581:root,testuser" >> "$BUNDLE_A/rootfs/etc/group" + echo "group:x:9001:testuser" >> "$BUNDLE_A/rootfs/etc/group" + echo "emptygroup:x:2222:" >> "$BUNDLE_A/rootfs/etc/group" + + # Repack the image. + umoci repack --image "${IMAGE}:${TAG}" "$BUNDLE_A" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Modify the user. + umoci config --image "${IMAGE}:${TAG}" --config.user="testuser:emptygroup" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Generate config.json. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + + # We *don't* want the users to be set because we don't have a rootfs. + + sane_run jq -SM '.process.user.uid' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 0 ] + sane_run jq -SM '.process.user.gid' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 0 ] + sane_run jq -SMr '.process.user.additionalGids' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "null" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.user 'user:group' --rootfs" { + BUNDLE_A="$(setup_tmpdir)" + BUNDLE_B="$(setup_tmpdir)" + BUNDLE_C="$(setup_tmpdir)" + + image-verify "${IMAGE}" + + # Unpack the image. + umoci unpack --image "${IMAGE}:${TAG}" "$BUNDLE_A" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE_A" + + # Modify /etc/passwd and /etc/group. + echo "testuser:x:1337:8888:test user:/my home dir :/bin/sh" >> "$BUNDLE_A/rootfs/etc/passwd" + echo "testgroup:x:2581:root,testuser" >> "$BUNDLE_A/rootfs/etc/group" + echo "group:x:9001:testuser" >> "$BUNDLE_A/rootfs/etc/group" + echo "emptygroup:x:2222:" >> "$BUNDLE_A/rootfs/etc/group" + + # Modify the user. + umoci config --image "${IMAGE}:${TAG}" --config.user="testuser:emptygroup" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Generate config.json. + umoci raw runtime-config --image "${IMAGE}:${TAG}" --rootfs "$BUNDLE_A/rootfs" "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + + # Make sure numeric config was actually set. + sane_run jq -SM '.process.user.uid' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 1337 ] + + # Make sure numeric config was actually set. + sane_run jq -SM '.process.user.gid' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 2222 ] + + # Check that HOME is set. + sane_run jq -SMr '.process.env[]' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + export $output + [[ "$HOME" == "/my home dir " ]] + + # Modify /etc/passwd and /etc/group. + sed -i -e 's|^testuser:x:1337:8888:test user:/my home dir :|testuser:x:3333:2321:a:/another home:|' "$BUNDLE_A/rootfs/etc/passwd" + sed -i -e 's|^emptygroup:x:2222:|emptygroup:x:4444:|' "$BUNDLE_A/rootfs/etc/group" + + # Unpack the image. + umoci raw runtime-config --image "${IMAGE}:${TAG}" --rootfs "$BUNDLE_A/rootfs" "$BUNDLE_C/config.json" + [ "$status" -eq 0 ] + + # Make sure numeric config was actually set. + sane_run jq -SM '.process.user.uid' "$BUNDLE_C/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 3333 ] + + # Make sure numeric config was actually set. + sane_run jq -SM '.process.user.gid' "$BUNDLE_C/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 4444 ] + + # Check that HOME is set. + sane_run jq -SMr '.process.env[]' "$BUNDLE_C/config.json" + [ "$status" -eq 0 ] + export $output + [[ "$HOME" == "/another home" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.user 'user:group' [non-existent user]" { + BUNDLE="$(setup_tmpdir)" + + # Modify the user. + umoci config --image "${IMAGE}:${TAG}" --config.user="testuser:emptygroup" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Generate config.json. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # We *don't* want the users to be set because we don't have a rootfs. + + sane_run jq -SM '.process.user.uid' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 0 ] + sane_run jq -SM '.process.user.gid' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 0 ] + sane_run jq -SMr '.process.user.additionalGids' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "null" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.user [numeric]" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" --config.user="1337:8888" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Make sure numeric config was actually set. + sane_run jq -SM '.process.user.uid' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 1337 ] + + # Make sure numeric config was actually set. + sane_run jq -SM '.process.user.gid' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [ "$output" -eq 8888 ] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.workingdir" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" --config.workingdir "/a/fake/directory" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Make sure numeric config was actually set. + sane_run jq -SM '.process.cwd' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [ "$output" = '"/a/fake/directory"' ] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --clear=config.env" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" --clear=config.env + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Make sure that nothing was set. + sane_run jq -SMr '.process.env | length' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == 0 ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.env" { + BUNDLE="$(setup_tmpdir)" + + # Modify env. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" --config.env "VARIABLE1=unused" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Modify the env again. + umoci config --image "${IMAGE}:${TAG}-new" --config.env "VARIABLE1=test" --config.env "VARIABLE2=what" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Make sure environment was set. + sane_run jq -SMr '.process.env[]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Make sure that they are all unique. + numDefs="${#lines[@]}" + numVars="$(echo "$output" | cut -d= -f1 | sort -u | wc -l)" + [ "$numDefs" -eq "$numVars" ] + + # Set the variables. + export $output + [[ "$VARIABLE1" == "test" ]] + [[ "$VARIABLE2" == "what" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --clear=config.{entrypoint or cmd}" { + BUNDLE_A="$(setup_tmpdir)" + BUNDLE_B="$(setup_tmpdir)" + + # Modify the entrypoint+cmd. + umoci config --image "${IMAGE}:${TAG}" --config.entrypoint "sh" --config.entrypoint "/here is some values/" --config.cmd "-c" --config.cmd "ls -la" --config.cmd="kek" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Clear the entrypoint. + umoci config --image "${IMAGE}:${TAG}" --tag="${TAG}-noentry" --clear=config.entrypoint + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image. + umoci raw runtime-config --image "${IMAGE}:${TAG}-noentry" "$BUNDLE_A/config.json" + [ "$status" -eq 0 ] + + # Ensure that the final args is only cmd. + sane_run jq -SMr 'reduce .process.args[] as $arg (""; . + $arg + ";")' "$BUNDLE_A/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "-c;ls -la;kek;" ]] + + # Clear the cmd. + umoci config --image "${IMAGE}:${TAG}" --tag="${TAG}-nocmd" --clear=config.cmd + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image. + umoci raw runtime-config --image "${IMAGE}:${TAG}-nocmd" "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + + # Ensure that the final args is only cmd. + sane_run jq -SMr 'reduce .process.args[] as $arg (""; . + $arg + ";")' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "sh;/here is some values/;" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.cmd" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --config.cmd "cat" --config.cmd "/this is a file with spaces" --config.cmd "-v" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Ensure that the final args is entrypoint+cmd. + sane_run jq -SMr 'reduce .process.args[] as $arg (""; . + $arg + ";")' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "cat;/this is a file with spaces;-v;" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --clear=config.[entrypoint+cmd]" { + BUNDLE="$(setup_tmpdir)" + + # Modify the entrypoint+cmd. + umoci config --image "${IMAGE}:${TAG}" --config.entrypoint "sh" --config.entrypoint "/here is some values/" --config.cmd "-c" --config.cmd "ls -la" --config.cmd="kek" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Clear the entrypoint and entrypoint. + umoci config --image "${IMAGE}:${TAG}" --tag="${TAG}-nocmdentry" --clear=config.entrypoint --clear=config.cmd + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image. + umoci raw runtime-config --image "${IMAGE}:${TAG}-nocmdentry" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Ensure that the final args is empty. + sane_run jq -SMr 'reduce .process.args[] as $arg (""; . + $arg + ";")' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + # TODO: This is almost certainly not going to be valid when config.json + # conversion is part of the spec. + [[ "$output" == "sh;" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.[entrypoint+cmd]" { + BUNDLE="$(setup_tmpdir)" + + # Modify the entrypoint+cmd. + umoci config --image "${IMAGE}:${TAG}" --config.entrypoint "sh" --config.cmd "-c" --config.cmd "ls -la" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Ensure that the final args is entrypoint+cmd. + sane_run jq -SMr 'reduce .process.args[] as $arg (""; . + $arg + ";")' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "sh;-c;ls -la;" ]] + + image-verify "${IMAGE}" +} + +# XXX: This test is somewhat dodgy (since we don't actually set anything other than the destination for a volume). +@test "umoci raw runtime-config --config.volume" { + BUNDLE_A="$(setup_tmpdir)" + BUNDLE_B="$(setup_tmpdir)" + BUNDLE_C="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --config.volume /volume --config.volume "/some nutty/path name/ here" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE_A/config.json" + [ "$status" -eq 0 ] + bundle-verify "$BUNDLE_A" + + # Get set of mounts + sane_run jq -SMr '.mounts[] | .destination' "$BUNDLE_A/config.json" + [ "$status" -eq 0 ] + + # Check mounts. + printf -- '%s\n' "${lines[*]}" | grep '^/volume$' + printf -- '%s\n' "${lines[*]}" | grep '^/some nutty/path name/ here$' + + # Make sure we're appending. + umoci config --image "${IMAGE}:${TAG}" --config.volume "/another volume" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + + # Get set of mounts + sane_run jq -SMr '.mounts[] | .destination' "$BUNDLE_B/config.json" + [ "$status" -eq 0 ] + + # Check mounts. + printf -- '%s\n' "${lines[*]}" | grep '^/volume$' + printf -- '%s\n' "${lines[*]}" | grep '^/some nutty/path name/ here$' + printf -- '%s\n' "${lines[*]}" | grep '^/another volume$' + + # Now clear the volumes + umoci config --image "${IMAGE}:${TAG}" --clear=config.volume --config.volume "/..final_volume" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE_C/config.json" + [ "$status" -eq 0 ] + + # Get set of mounts + sane_run jq -SMr '.mounts[] | .destination' "$BUNDLE_C/config.json" + [ "$status" -eq 0 ] + + # Check mounts. + ! ( printf -- '%s\n' "${lines[*]}" | grep '^/volume$' ) + ! ( printf -- '%s\n' "${lines[*]}" | grep '^/some nutty/path name/ here$' ) + ! ( printf -- '%s\n' "${lines[*]}" | grep '^/another volume$' ) + printf -- '%s\n' "${lines[*]}" | grep '^/\.\.final_volume$' + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --[os+architecture]" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + # XXX: We can't test anything other than --os=linux because our generator bails for non-Linux OSes. + umoci config --image "${IMAGE}:${TAG}" --os "linux" --architecture "mips64" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Check that OS was set properly. + sane_run jq -SMr '.platform.os' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "linux" ]] + + # Check that arch was set properly. + sane_run jq -SMr '.platform.arch' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "mips64" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.label" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" \ + --clear=config.labels --clear=manifest.annotations \ + --config.label="com.cyphar.test=1" --config.label="com.cyphar.empty=" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + sane_run jq -SMr '.annotations["com.cyphar.test"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "1" ]] + + sane_run jq -SMr '.annotations["com.cyphar.empty"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "" ]] + + image-verify "${IMAGE}" +} + +# XXX: This will probably become non-compliant with the spec once the whole +# conversion logic is defined. +@test "umoci raw runtime-config --manifest.annotation" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" \ + --clear=config.labels --clear=manifest.annotations \ + --manifest.annotation="com.cyphar.test=1" --manifest.annotation="com.cyphar.empty=" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + sane_run jq -SMr '.annotations["com.cyphar.test"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "1" ]] + + sane_run jq -SMr '.annotations["com.cyphar.empty"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "" ]] + + image-verify "${IMAGE}" +} + +@test "umoci raw runtime-config --config.label --manifest.annotation" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" \ + --clear=config.labels --clear=manifest.annotations \ + --config.label="com.cyphar.label_test={another value}" --config.label="com.cyphar.label_empty=" \ + --manifest.annotation="com.cyphar.manifest_test= another valu=e " --manifest.annotation="com.cyphar.manifest_empty=" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + sane_run jq -SMr '.annotations["com.cyphar.label_test"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "{another value}" ]] + + sane_run jq -SMr '.annotations["com.cyphar.label_empty"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "" ]] + + sane_run jq -SMr '.annotations["com.cyphar.manifest_test"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == " another valu=e " ]] + + sane_run jq -SMr '.annotations["com.cyphar.manifest_empty"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "" ]] + + image-verify "${IMAGE}" +} + +# XXX: This is currently not in any spec. So we'll just test our own behaviour +# here and we can fix it after opencontainers/image-spec#479 is fixed. +@test "umoci raw runtime-config --config.label --manifest.annotation [clobber]" { + BUNDLE="$(setup_tmpdir)" + + # Modify none of the configuration. + umoci config --image "${IMAGE}:${TAG}" --tag "${TAG}-new" \ + --clear=config.labels --clear=manifest.annotations \ + --config.label="com.cyphar.test= this_is SOEM VALUE" --config.label="com.cyphar.label_empty=" \ + --manifest.annotation="com.cyphar.test== __ --a completely different VALuE " --manifest.annotation="com.cyphar.manifest_empty=" + [ "$status" -eq 0 ] + image-verify "${IMAGE}" + + # Unpack the image again. + umoci raw runtime-config --image "${IMAGE}:${TAG}-new" "$BUNDLE/config.json" + [ "$status" -eq 0 ] + + # Manifest beats config. + sane_run jq -SMr '.annotations["com.cyphar.test"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "= __ --a completely different VALuE " ]] + + sane_run jq -SMr '.annotations["com.cyphar.label_empty"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "" ]] + + sane_run jq -SMr '.annotations["com.cyphar.manifest_empty"]' "$BUNDLE/config.json" + [ "$status" -eq 0 ] + [[ "$output" == "" ]] + + image-verify "${IMAGE}" +} From 7fb831cb378dc18eba6d2cd784e3de1d7c8747f6 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 10 Apr 2017 20:18:40 +1000 Subject: [PATCH 5/7] CHANGELOG: update for umoci-raw-runtime-config(1) Signed-off-by: Aleksa Sarai --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5007b19d2..313ce7f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). bit weird (`cmd` and `entrypoint` aren't treated atomically) this makes the UX more consistent while we come up with a better `cmd` and `entrypoint` UX. openSUSE/umoci#107 +- New subcommand: `umoci raw runtime-config`. It generates the runtime-spec + config.json for a particular image without also unpacking the root + filesystem, allowing for users of `umoci` that are regularly parsing + `config.json` without caring about the root filesystem to be more efficient. + However, a downside of this approach is that some image-spec fields + (`Config.User`) require a root filesystem in order to make sense, which is + why this command is hidden under the `umoci-raw(1)` subcommand (to make sure + only users that understand what they're doing use it). openSUSE/umoci#110 ### Changed - `umoci`'s `oci/cas` and `oci/config` libraries have been massively refactored From c162524a3c8410295f15476f945122c62142d1b5 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 10 Apr 2017 23:04:03 +1000 Subject: [PATCH 6/7] man: add docs for umoci-raw-runtime-config(1) Signed-off-by: Aleksa Sarai --- man/umoci-raw-config.1.md | 1 + man/umoci-raw-runtime-config.1.md | 77 +++++++++++++++++++++++++++++++ man/umoci-raw.1.md | 26 +++++++++++ 3 files changed, 104 insertions(+) create mode 120000 man/umoci-raw-config.1.md create mode 100644 man/umoci-raw-runtime-config.1.md create mode 100644 man/umoci-raw.1.md diff --git a/man/umoci-raw-config.1.md b/man/umoci-raw-config.1.md new file mode 120000 index 000000000..f6b40e2ba --- /dev/null +++ b/man/umoci-raw-config.1.md @@ -0,0 +1 @@ +umoci-raw-runtime-config.1.md \ No newline at end of file diff --git a/man/umoci-raw-runtime-config.1.md b/man/umoci-raw-runtime-config.1.md new file mode 100644 index 000000000..534d00dce --- /dev/null +++ b/man/umoci-raw-runtime-config.1.md @@ -0,0 +1,77 @@ +% umoci-raw-runtime-config(1) # umoci raw runtime-config - Generate an OCI runtime configuration for an image +% Aleksa Sarai +% DECEMBER 2016 +# NAME +umoci raw runtime-config - Generate an OCI runtime configuration for an image + +# SYNOPSIS +**umoci raw runtime-config** +**--image**=*image*[:*tag*] +[**--rootfs**=*rootfs*] +[**--rootless**] +*config* + +**umoci raw config** +**--image**=*image*[:*tag*] +[**--rootfs**=*rootfs*] +[**--rootless**] +*config* + +# DESCRIPTION +Generate a new OCI runtime configuration from an image, without extracting the +rootfs of said image. The configuration is written to the path given by +*config*, overwriting it if it exists already. This is one of the operations +done by **umoci-unpack**(1) when generating the runtime bundle, but because of +the overhead of extracting a root filesystem, **umoci-unpack**(1) is not +practical to be used many times if the user doesn't actually want to use the +root filesystem. Some fields require a root filesystem as a "source of truth", +and a source root filesystem can be specified using **--rootfs**. The other +flags have the same effects as with **umoci-unpack**(1). + +Note however that the output of **umoci-raw-runtime-config**(1) is not +necessarily identical to the output from **umoci-unpack**(1). This is +especially true if **--rootfs** is not specified, which results in **umoci**(1) +leaving fields in the runtime spec to their defaults if computing their values +would require using the root filesystem as a source-of-truth. + +# OPTIONS +The global options are defined in **umoci**(1). + +**--image**=*image*[:*tag*] + The OCI image tag which will be extracted to the *bundle*. *image* must be a + path to a valid OCI image and *tag* must be a valid tag in the image. If + *tag* is not provided it defaults to "latest". + +**--rootfs**=*rootfs* + Use *rootfs* as a secondary source of truth when generating the runtime + configuration (this is especially important for *Config.User* conversion). If + unspecified, any runtime fields that require a secondary source of truth to + be filled with be left in their default values. This may result in + discrepancies between the output of **umoci-unpack**(1) and + **umoci-raw-runtime-config**(1). + +**--rootless** + Generate a rootless container configuration, similar to the configuration + produced by **umoci-unpack**(1) when provided the **--rootless** flag. + +# EXAMPLE +The following downloads an image from a **docker**(1) registry using +**skopeo**(1) and then generates the *config.json* for that image. + +``` +% skopeo copy docker://opensuse/amd64:42.2 oci:image:latest +% umoci raw runtime-config --image image:latest config.json +``` + +If a root filesystem is already present, it is possible to specify it with the +**--rootfs** flag. This will source the root filesystem for conversion +operations that necessitate it. + +``` +% skopeo copy docker://opensuse/amd64:42.2 oci:image:latest +# umoci unpack --image image bundle +% umoci raw runtime-generate --image image --rootfs bundle/rootfs config.json +``` + +# SEE ALSO +**umoci**(1), **umoci-repack**(1), **runc**(8) diff --git a/man/umoci-raw.1.md b/man/umoci-raw.1.md new file mode 100644 index 000000000..c2170f122 --- /dev/null +++ b/man/umoci-raw.1.md @@ -0,0 +1,26 @@ +% umoci-raw(1) # umoci raw - Advanced internal image tooling +% Aleksa Sarai +% APRIL 2017 +# NAME +umoci raw - Advanced internal image tooling + +# SYNOPSIS +**umoci raw** +*command* [*args*] + +# DESCRIPTION +**umoci-raw**(1) is a subcommand that contains further subcommands specifically +intended for "advanced" usage of **umoci**(1). Unless you are familiar with the +details of the OCI image specification, or otherwise understand what the +implications of using these features is, it is not recommended. The top-level +tools (**umoci-unpack**(1) and so on) should be sufficient for most use-cases. + +# COMMANDS + +**runtime-config, config** + Generate an OCI runtime configuration for an image, without the rootfs. See + **umoci-raw-runtime-config**(1) for more detailed usage information. + +# SEE ALSO +**umoci**(1), +**umoci-raw-runtime-config**(1) From 06871d9c3ec80492a06ac85e1aa530ffbdae38fc Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 10 Apr 2017 23:04:51 +1000 Subject: [PATCH 7/7] man: various cleanups The old system of symlinks was bad and kept breaking quite often, so switch to symlinking the sources not the results. In addition, clean up some of the overly-liberal usage of []. Signed-off-by: Aleksa Sarai --- man/umoci-config.1.md | 52 +++++++++++++++++++++---------------------- man/umoci-ls.1 | 1 - man/umoci-ls.1.md | 1 + man/umoci-rm.1 | 1 - man/umoci-rm.1.md | 1 + man/umoci.1.md | 30 ++++++++++++++++--------- 6 files changed, 48 insertions(+), 38 deletions(-) delete mode 120000 man/umoci-ls.1 create mode 120000 man/umoci-ls.1.md delete mode 120000 man/umoci-rm.1 create mode 120000 man/umoci-rm.1.md diff --git a/man/umoci-config.1.md b/man/umoci-config.1.md index d01363f95..3660aae67 100644 --- a/man/umoci-config.1.md +++ b/man/umoci-config.1.md @@ -13,19 +13,19 @@ umoci config - Modifies the configuration of an OCI image [**--history.author**=*author*] [**--history-created**=*date*] [**--clear**=*value*] -[**--config.user**=[*value*]] -[**--config.exposedports**=[*value*]] -[**--config.env**=[*value*]] -[**--config.entrypoint**=[*value*]] -[**--config.cmd**=[*value*]] -[**--config.volume**=[*value*]] -[**--config.label**=[*value*]] -[**--config.workingdir**=[*value*]] -[**--created**=[*value*]] -[**--author**=[*value*]] -[**--architecture**=[*value*]] -[**--os**=[*value*]] -[**--manifest.annotation**=[*value*]] +[**--config.user**=*value*] +[**--config.exposedports**=*value*] +[**--config.env**=*value*] +[**--config.entrypoint**=*value*] +[**--config.cmd**=*value*] +[**--config.volume**=*value*] +[**--config.label**=*value*] +[**--config.workingdir**=*value*] +[**--created**=*value*] +[**--author**=*value*] +[**--architecture**=*value*] +[**--os**=*value*] +[**--manifest.annotation**=*value*] # DESCRIPTION Modify the configuration and manifest data for a particular tagged OCI image. @@ -85,19 +85,19 @@ The global options are defined in **umoci**(1). The following commands all set their corresponding values in the configuration or image manifest. For more information see [the OCI image specification][1]. -* **--config.user**=[*value*] -* **--config.exposedports**=[*value*] -* **--config.env**=[*value*] -* **--config.entrypoint**=[*value*] -* **--config.cmd**=[*value*] -* **--config.volume**=[*value*] -* **--config.label**=[*value*] -* **--config.workingdir**=[*value*] -* **--created**=[*value*] -* **--author**=[*value*] -* **--architecture**=[*value*] -* **--os**=[*value*] -* **--manifest.annotation**=[*value*] +* **--config.user**=*value* +* **--config.exposedports**=*value* +* **--config.env**=*value* +* **--config.entrypoint**=*value* +* **--config.cmd**=*value* +* **--config.volume**=*value* +* **--config.label**=*value* +* **--config.workingdir**=*value* +* **--created**=*value* +* **--author**=*value* +* **--architecture**=*value* +* **--os**=*value* +* **--manifest.annotation**=*value* # EXAMPLE diff --git a/man/umoci-ls.1 b/man/umoci-ls.1 deleted file mode 120000 index 1c1a9e062..000000000 --- a/man/umoci-ls.1 +++ /dev/null @@ -1 +0,0 @@ -umoci-list.1 \ No newline at end of file diff --git a/man/umoci-ls.1.md b/man/umoci-ls.1.md new file mode 120000 index 000000000..39f15486e --- /dev/null +++ b/man/umoci-ls.1.md @@ -0,0 +1 @@ +umoci-list.1.md \ No newline at end of file diff --git a/man/umoci-rm.1 b/man/umoci-rm.1 deleted file mode 120000 index 80c67f75c..000000000 --- a/man/umoci-rm.1 +++ /dev/null @@ -1 +0,0 @@ -umoci-remove.1 \ No newline at end of file diff --git a/man/umoci-rm.1.md b/man/umoci-rm.1.md new file mode 120000 index 000000000..562be3d04 --- /dev/null +++ b/man/umoci-rm.1.md @@ -0,0 +1 @@ +umoci-remove.1.md \ No newline at end of file diff --git a/man/umoci.1.md b/man/umoci.1.md index d26446132..283d9f0c9 100644 --- a/man/umoci.1.md +++ b/man/umoci.1.md @@ -36,34 +36,44 @@ doing a high-level operation such as **umoci-repack**(1)). # COMMANDS **init** - Create a new OCI layout. See **umoci-init**(1) for more detailed usage information. + Create a new OCI layout. See **umoci-init**(1) for more detailed usage + information. **new** - Creates a blank tagged OCI image. See **umoci-new**(1) for more detailed usage information. + Creates a blank tagged OCI image. See **umoci-new**(1) for more detailed + usage information. **unpack** - Unpacks a tagged image into an OCI runtime bundle. See **umoci-unpack**(1) for more detailed usage information. + Unpacks a tagged image into an OCI runtime bundle. See **umoci-unpack**(1) + for more detailed usage information. **repack** - Repacks an OCI runtime bundle into a tagged image. See **umoci-repack**(1) for more detailed usage information. + Repacks an OCI runtime bundle into a tagged image. See **umoci-repack**(1) + for more detailed usage information. **config** - Modifies the image configuration of an OCI image. See **umoci-config**(1) for more detailed usage information. + Modifies the image configuration of an OCI image. See **umoci-config**(1) for + more detailed usage information. **stat** - Displays status information of an image manifest. See **umoci-stat**(1) for more detailed usage information. + Displays status information of an image manifest. See **umoci-stat**(1) for + more detailed usage information. **tag** - Creates a new tag in an OCI image. See **umoci-tag**(1) for more detailed usage information. + Creates a new tag in an OCI image. See **umoci-tag**(1) for more detailed + usage information. **remove, rm** - Removes a tag from an OCI image. See **umoci-remove**(1) for more detailed usage information. + Removes a tag from an OCI image. See **umoci-remove**(1) for more detailed + usage information. **list, ls** - Lists the set of tags in an OCI image. See **umoci-list**(1) for more detailed usage information. + Lists the set of tags in an OCI image. See **umoci-list**(1) for more + detailed usage information. **gc** - Garbage collects all unreferenced OCI image blobs. See **umoci-gc**(1) for more detailed usage information. + Garbage collects all unreferenced OCI image blobs. See **umoci-gc**(1) for + more detailed usage information. # SEE ALSO **umoci-init**(1),