Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
}