Skip to content

Commit

Permalink
translate: Stub in a translation framework
Browse files Browse the repository at this point in the history
Add tooling to translate higher-level configs into the basic OCI
config.  On IRC, Julz floated a linux.namespaces[].fromContainer as a
higher-level version of linux.namespaces[].path for emulating exec
[1].  That makes sense to me, and Mrunal is open to something like
[2]:

  $ ocitools generate --template=high-level-config.json --translate=fromContainer --runtime=runc

The interface{} -> rspec.Spec conversion happens through a JSON
serialization trick suggested by 梁辰晔 (Liang Chenye) [3] after the
translations, because before the translations the template may contain
JSON that's not represented in the OCI Spec structure
(e.g. fromContainer in namespace entries).

The /proc lookup could still be improved:

* Lookup the mount path (currently hard-coded to /proc) in
  /proc/self/mounts.  This would extend support to users who had
  mounted proc at a different location, but it's hard to find
  self/mounts unless you already know where proc is mounted ;).
* Compare /proc with the state JSON's implied PID namespace [4].  This
  would extend support to users in a different PID namespace than the
  one in which the state JSON's 'pid' entry applied.

But those are likely to be corner cases (and until something like [4]
lands, the latter is not possible).  So I'm punting them into the
future, where they can be fixed as we discover methods to fix them.

[1]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/%23opencontainers.2016-04-27.log.html#t2016-04-27T20:32:09
[2]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/%23opencontainers.2016-04-28.log.html#t2016-04-28T16:12:48
[3]: #54 (comment)
[4]: https://groups.google.com/a/opencontainers.org/forum/#!topic/dev/ujtABQoCmgk
     Subject: Linux: Adding the PID namespace inode to state JSON
     Date: Wed, 30 Dec 2015 21:34:57 -0800
     Message-ID: <[email protected]>

Signed-off-by: W. Trevor King <[email protected]>
  • Loading branch information
wking committed May 14, 2016
1 parent efd8d8f commit 10fe0e9
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 5 deletions.
36 changes: 31 additions & 5 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/codegangsta/cli"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/syndtr/gocapability/capability"
"github.com/opencontainers/ocitools/translate"
)

var generateFlags = []cli.Flag{
Expand Down Expand Up @@ -55,7 +56,9 @@ var generateFlags = []cli.Flag{
cli.StringSliceFlag{Name: "seccomp-arch", Usage: "specifies Additional architectures permitted to be used for system calls"},
cli.StringSliceFlag{Name: "seccomp-syscalls", Usage: "specifies Additional architectures permitted to be used for system calls, e.g Name:Action:Arg1_index/Arg1_value/Arg1_valuetwo/Arg1_op, Arg2_index/Arg2_value/Arg2_valuetwo/Arg2_op "},
cli.StringSliceFlag{Name: "seccomp-allow", Usage: "specifies syscalls to be added to allowed"},
cli.StringFlag{Name: "runtime", Usage: "select the runtime command (used for some translations)"},
cli.StringFlag{Name: "template", Usage: "base template to use for creating the configuration"},
cli.StringSliceFlag{Name: "translate", Usage: "translate higher level constructs"},
cli.StringSliceFlag{Name: "label", Usage: "add annotations to the configuration e.g. key=value"},
}

Expand Down Expand Up @@ -93,12 +96,35 @@ var generateCommand = cli.Command{
}
}

err := modify(spec, context)
translations := context.StringSlice("translate")
for _, translation := range translations {
translator, ok := translate.Translators[translation]
if !ok {
logrus.Fatalf("unrecognized translation: %s", translation)
}
var err error
spec, err = translator(spec, context)
if err != nil {
logrus.Fatal(err)
}
}

buf, err := json.Marshal(spec)
if err != nil {
logrus.Fatal(err)
}
var strictSpec rspec.Spec
err = json.Unmarshal(buf, &strictSpec)
if err != nil {
logrus.Fatal(err)
}

err = modify(&strictSpec, context)
if err != nil {
logrus.Fatal(err)
}
cName := "config.json"
data, err := json.MarshalIndent(&spec, "", "\t")
data, err := json.MarshalIndent(&strictSpec, "", "\t")
if err != nil {
logrus.Fatal(err)
}
Expand All @@ -108,7 +134,7 @@ var generateCommand = cli.Command{
},
}

func loadTemplate(path string) (spec *rspec.Spec, err error) {
func loadTemplate(path string) (spec interface{}, err error) {
cf, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
Expand Down Expand Up @@ -709,7 +735,7 @@ func removeNamespace(namespaces *[]rspec.Namespace, namespaceType rspec.Namespac

func sPtr(s string) *string { return &s }

func getDefaultTemplate() *rspec.Spec {
func getDefaultTemplate() interface{} {
spec := rspec.Spec{
Version: rspec.Version,
Platform: rspec.Platform{
Expand Down Expand Up @@ -824,5 +850,5 @@ func getDefaultTemplate() *rspec.Spec {
},
}

return &spec
return spec
}
19 changes: 19 additions & 0 deletions man/ocitools-generate.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ inside of the container.
**--rootfs**=ROOTFSPATH
Path to the rootfs

**--runtime**=COMMAND
Set the runtime command, which is used for state JSON queries in translations like **--translate=fromContainer**.

**--seccomp-arch**=ARCH
Specifies Additional architectures permitted to be used for system calls.
By default if you turn on seccomp, only the host architecture will be allowed.
Expand Down Expand Up @@ -187,6 +190,10 @@ inside of the container.
This command mounts a `tmpfs` at `/tmp` within the container. The supported mount options are the same as the Linux default `mount` flags. If you do not specify any options, the systems uses the following options:
`rw,noexec,nosuid,nodev,size=65536k`.

**--translate**=TRANSLATION
Apply various higher-level spec translations. Available
translations are listed in the *Translations* section.

**--uid**=UID
Sets the UID used within the container.

Expand All @@ -203,6 +210,18 @@ inside of the container.
is unset, create a new namespace. The special *PATH* `host` removes
any existing UTS namespace from the configuration.

## Translations

**fromContainer**
The base OCI spec requires a namespace path in
`linux.namespaces[].path` to join a namespace. However, looking up
that path in `/proc` can be tedious. With a target container ID in
`linux.namespaces[].fromContainer`, the **fromContainer**
translation will query the runtime (set with **--runtime**) for the
state JSON, extract the container PID from that JSON, find the
appropriate namespace path for that PID in `/proc`, and insert that
path in the translated configuration as `linux.namespaces[].path`.

# EXAMPLES

## Generating container in read-only mode
Expand Down
95 changes: 95 additions & 0 deletions translate/from_container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package translate

import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"path/filepath"

"github.com/codegangsta/cli"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)

func FromContainer(data interface{}, context *cli.Context) (translated interface{}, err error) {
dataMap, ok := data.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("data is not a map[string]interface{}: %s", data)
}

linuxInterface, ok := dataMap["linux"]
if !ok {
return data, nil
}

linux, ok := linuxInterface.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("data.linux is not a map[string]interface{}: %s", linuxInterface)
}

namespacesInterface, ok := linux["namespaces"]
if !ok {
return data, nil
}

namespaces, ok := namespacesInterface.([]interface{})
if !ok {
return nil, fmt.Errorf("data.linux.namespaces is not an array: %s", namespacesInterface)
}

for index, namespaceInterface := range namespaces {
namespace, ok := namespaceInterface.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("data.linux.namespaces[%d] is not a map[string]interface{}: %s", index, namespaceInterface)
}
err := namespaceFromContainer(&namespace, index, context)
if err != nil {
return nil, err
}
}

return data, nil
}

func namespaceFromContainer(namespace *map[string]interface{}, index int, context *cli.Context) (err error) {
fromContainerInterface, ok := (*namespace)["fromContainer"]
if ok {
fromContainer, ok := fromContainerInterface.(string)
if !ok {
return fmt.Errorf("data.linux.namespaces[%d].fromContainer is not a string: %s", index, fromContainerInterface)
}
delete(*namespace, "fromContainer")
runtime := context.String("runtime")
if (len(runtime) == 0) {
return fmt.Errorf("translating fromContainer requires a non-empty --runtime")
}
command := exec.Command(runtime, "state", fromContainer)
var out bytes.Buffer
command.Stdout = &out
err := command.Run()
if err != nil {
return err
}
var state rspec.State
err = json.Unmarshal(out.Bytes(), &state)
if err != nil {
return err
}
namespaceTypeInterface, ok := (*namespace)["type"]
if !ok {
return fmt.Errorf("data.linux.namespaces[%d].type is missing: %s", index, fromContainerInterface)
}
namespaceType, ok := namespaceTypeInterface.(string)
if !ok {
return fmt.Errorf("data.linux.namespaces[%d].type is not a string: %s", index, namespaceTypeInterface)
}
switch namespaceType {
case "network": namespaceType = "net"
case "mount": namespaceType = "mnt"
}
proc := "/proc" // FIXME: lookup in /proc/self/mounts, check right namespace
path := filepath.Join(proc, fmt.Sprint(state.Pid), "ns", namespaceType)
(*namespace)["path"] = path
}
return nil
}
20 changes: 20 additions & 0 deletions translate/translate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Package translate handles translation between configuration
specifications.
For example, it allows you to generate OCI-compliant config.json from
a higher-level configuration language.
*/
package translate

import (
"github.com/codegangsta/cli"
)

// Translate maps JSON from one specification to another.
type Translate func(data interface{}, context *cli.Context) (translated interface{}, err error)

// Translators is a map from translator names to Translate functions.
var Translators = map[string]Translate{
"fromContainer": FromContainer,
}

0 comments on commit 10fe0e9

Please sign in to comment.