Skip to content

Commit 10fe0e9

Browse files
committed
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). 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]>
1 parent efd8d8f commit 10fe0e9

File tree

4 files changed

+165
-5
lines changed

4 files changed

+165
-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: 19 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,10 @@ 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. Available
195+
translations are listed in the *Translations* section.
196+
190197
**--uid**=UID
191198
Sets the UID used within the container.
192199

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

213+
## Translations
214+
215+
**fromContainer**
216+
The base OCI spec requires a namespace path in
217+
`linux.namespaces[].path` to join a namespace. However, looking up
218+
that path in `/proc` can be tedious. With a target container ID in
219+
`linux.namespaces[].fromContainer`, the **fromContainer**
220+
translation will query the runtime (set with **--runtime**) for the
221+
state JSON, extract the container PID from that JSON, find the
222+
appropriate namespace path for that PID in `/proc`, and insert that
223+
path in the translated configuration as `linux.namespaces[].path`.
224+
206225
# EXAMPLES
207226

208227
## Generating container in read-only mode

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)