Skip to content

Commit f7f25b3

Browse files
committed
WIP: translate: Stub in a translation framework
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). This commit still needs (marked by FIXMEs): * Lookup /proc in /proc/self/mounts (recursive?). * Compare /proc with state JSON namespace [4]. * A way to handle nested definition lists in man pages. I expect we should drop go-md2man in favor of a more established man-page format (e.g. AsciiDoc). [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: <20151231053457.GG3680@odin.tremily.us> Signed-off-by: W. Trevor King <wking@tremily.us>
1 parent efd8d8f commit f7f25b3

File tree

4 files changed

+159
-5
lines changed

4 files changed

+159
-5
lines changed

generate.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/codegangsta/cli"
1414
rspec "github.com/opencontainers/runtime-spec/specs-go"
1515
"github.com/syndtr/gocapability/capability"
16+
"github.com/opencontainers/ocitools/translate"
1617
)
1718

1819
var generateFlags = []cli.Flag{
@@ -55,7 +56,9 @@ var generateFlags = []cli.Flag{
5556
cli.StringSliceFlag{Name: "seccomp-arch", Usage: "specifies Additional architectures permitted to be used for system calls"},
5657
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 "},
5758
cli.StringSliceFlag{Name: "seccomp-allow", Usage: "specifies syscalls to be added to allowed"},
59+
cli.StringFlag{Name: "runtime", Usage: "select the runtime command (used for some translations)"},
5860
cli.StringFlag{Name: "template", Usage: "base template to use for creating the configuration"},
61+
cli.StringSliceFlag{Name: "translate", Usage: "translate higher level constructs"},
5962
cli.StringSliceFlag{Name: "label", Usage: "add annotations to the configuration e.g. key=value"},
6063
}
6164

@@ -93,12 +96,35 @@ var generateCommand = cli.Command{
9396
}
9497
}
9598

96-
err := modify(spec, context)
99+
translations := context.StringSlice("translate")
100+
for _, translation := range translations {
101+
translator, ok := translate.Translators[translation]
102+
if !ok {
103+
logrus.Fatalf("unrecognized translation: %s", translation)
104+
}
105+
var err error
106+
spec, err = translator(spec, context)
107+
if err != nil {
108+
logrus.Fatal(err)
109+
}
110+
}
111+
112+
buf, err := json.Marshal(spec)
113+
if err != nil {
114+
logrus.Fatal(err)
115+
}
116+
var strictSpec rspec.Spec
117+
err = json.Unmarshal(buf, &strictSpec)
118+
if err != nil {
119+
logrus.Fatal(err)
120+
}
121+
122+
err = modify(&strictSpec, context)
97123
if err != nil {
98124
logrus.Fatal(err)
99125
}
100126
cName := "config.json"
101-
data, err := json.MarshalIndent(&spec, "", "\t")
127+
data, err := json.MarshalIndent(&strictSpec, "", "\t")
102128
if err != nil {
103129
logrus.Fatal(err)
104130
}
@@ -108,7 +134,7 @@ var generateCommand = cli.Command{
108134
},
109135
}
110136

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

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

712-
func getDefaultTemplate() *rspec.Spec {
738+
func getDefaultTemplate() interface{} {
713739
spec := rspec.Spec{
714740
Version: rspec.Version,
715741
Platform: rspec.Platform{
@@ -824,5 +850,5 @@ func getDefaultTemplate() *rspec.Spec {
824850
},
825851
}
826852

827-
return &spec
853+
return spec
828854
}

man/ocitools-generate.1.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ inside of the container.
147147
**--rootfs**=ROOTFSPATH
148148
Path to the rootfs
149149

150+
**--runtime**=COMMAND
151+
Set the runtime command, which is used for state JSON queries in translations like **--translate=fromContainer**.
152+
150153
**--seccomp-arch**=ARCH
151154
Specifies Additional architectures permitted to be used for system calls.
152155
By default if you turn on seccomp, only the host architecture will be allowed.
@@ -187,6 +190,16 @@ inside of the container.
187190
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:
188191
`rw,noexec,nosuid,nodev,size=65536k`.
189192

193+
**--translate**=TRANSLATION
194+
Apply various higher-level spec translations.
195+
Available translations:
196+
197+
**fromContainer**
198+
FIXME: This needs to be indented as a sub-definition-list.
199+
The base OCI spec requires a namespace path in **`linux.namespaces[].path`** to join a namespace.
200+
However, looking up that path in `/proc` can be tedious.
201+
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`**.
202+
190203
**--uid**=UID
191204
Sets the UID used within the container.
192205

translate/from_container.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package translate
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"os/exec"
8+
"path/filepath"
9+
10+
"github.com/codegangsta/cli"
11+
rspec "github.com/opencontainers/runtime-spec/specs-go"
12+
)
13+
14+
func FromContainer(data interface{}, context *cli.Context) (translated interface{}, err error) {
15+
dataMap, ok := data.(map[string]interface{})
16+
if !ok {
17+
return nil, fmt.Errorf("data is not a map[string]interface{}: %s", data)
18+
}
19+
20+
linuxInterface, ok := dataMap["linux"]
21+
if !ok {
22+
return data, nil
23+
}
24+
25+
linux, ok := linuxInterface.(map[string]interface{})
26+
if !ok {
27+
return nil, fmt.Errorf("data.linux is not a map[string]interface{}: %s", linuxInterface)
28+
}
29+
30+
namespacesInterface, ok := linux["namespaces"]
31+
if !ok {
32+
return data, nil
33+
}
34+
35+
namespaces, ok := namespacesInterface.([]interface{})
36+
if !ok {
37+
return nil, fmt.Errorf("data.linux.namespaces is not an array: %s", namespacesInterface)
38+
}
39+
40+
for index, namespaceInterface := range namespaces {
41+
namespace, ok := namespaceInterface.(map[string]interface{})
42+
if !ok {
43+
return nil, fmt.Errorf("data.linux.namespaces[%d] is not a map[string]interface{}: %s", index, namespaceInterface)
44+
}
45+
err := namespaceFromContainer(&namespace, index, context)
46+
if err != nil {
47+
return nil, err
48+
}
49+
}
50+
51+
return data, nil
52+
}
53+
54+
func namespaceFromContainer(namespace *map[string]interface{}, index int, context *cli.Context) (err error) {
55+
fromContainerInterface, ok := (*namespace)["fromContainer"]
56+
if ok {
57+
fromContainer, ok := fromContainerInterface.(string)
58+
if !ok {
59+
return fmt.Errorf("data.linux.namespaces[%d].fromContainer is not a string: %s", index, fromContainerInterface)
60+
}
61+
delete(*namespace, "fromContainer")
62+
runtime := context.String("runtime")
63+
if (len(runtime) == 0) {
64+
return fmt.Errorf("translating fromContainer requires a non-empty --runtime")
65+
}
66+
command := exec.Command(runtime, "state", fromContainer)
67+
var out bytes.Buffer
68+
command.Stdout = &out
69+
err := command.Run()
70+
if err != nil {
71+
return err
72+
}
73+
var state rspec.State
74+
err = json.Unmarshal(out.Bytes(), &state)
75+
if err != nil {
76+
return err
77+
}
78+
namespaceTypeInterface, ok := (*namespace)["type"]
79+
if !ok {
80+
return fmt.Errorf("data.linux.namespaces[%d].type is missing: %s", index, fromContainerInterface)
81+
}
82+
namespaceType, ok := namespaceTypeInterface.(string)
83+
if !ok {
84+
return fmt.Errorf("data.linux.namespaces[%d].type is not a string: %s", index, namespaceTypeInterface)
85+
}
86+
switch namespaceType {
87+
case "network": namespaceType = "net"
88+
case "mount": namespaceType = "mnt"
89+
}
90+
proc := "/proc" // FIXME: lookup in /proc/self/mounts, check right namespace
91+
path := filepath.Join(proc, fmt.Sprint(state.Pid), "ns", namespaceType)
92+
(*namespace)["path"] = path
93+
}
94+
return nil
95+
}

translate/translate.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Package translate handles translation between configuration
3+
specifications.
4+
5+
For example, it allows you to generate OCI-compliant config.json from
6+
a higher-level configuration language.
7+
*/
8+
package translate
9+
10+
import (
11+
"github.com/codegangsta/cli"
12+
)
13+
14+
// Translate maps JSON from one specification to another.
15+
type Translate func(data interface{}, context *cli.Context) (translated interface{}, err error)
16+
17+
// Translators is a map from translator names to Translate functions.
18+
var Translators = map[string]Translate{
19+
"fromContainer": FromContainer,
20+
}

0 commit comments

Comments
 (0)