Skip to content

Commit

Permalink
cmd: umoci: add umoci-raw-runtime-config(1)
Browse files Browse the repository at this point in the history
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 <asarai@suse.de>
  • Loading branch information
cyphar committed Apr 9, 2017
1 parent 07b8135 commit 324067a
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 4 deletions.
8 changes: 4 additions & 4 deletions cmd/umoci/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,15 @@ func main() {
tagRemoveCommand,
tagListCommand,
statCommand,
rawSubcommand,
}

app.Metadata = map[string]interface{}{}

// 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
Expand All @@ -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 {
Expand All @@ -148,9 +149,8 @@ func main() {
}
return nil
}
cmd = uxLayout(cmd)
*cmd = uxLayout(*cmd)
}
app.Commands[idx] = cmd
}

// Actually run umoci.
Expand Down
171 changes: 171 additions & 0 deletions cmd/umoci/raw-runtime-config.go
Original file line number Diff line number Diff line change
@@ -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 <image-path>[:<tag>] [--rootfs <rootfs>] <config.json>
Where "<image-path>" is the path to the OCI image, "<tag>" is the name of the
tagged image to unpack (if not specified, defaults to "latest"), "<rootfs>" is
a rootfs to use as a supplementary "source of truth" for certain generation
operations and "<config.json>" 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 <config.json>")
}
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
}
38 changes: 38 additions & 0 deletions cmd/umoci/raw.go
Original file line number Diff line number Diff line change
@@ -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 <command> [<args>...]
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,
},
}
9 changes: 9 additions & 0 deletions cmd/umoci/utils_ux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 324067a

Please sign in to comment.