Skip to content

Commit 70a918d

Browse files
author
Sergiusz Urbaniak
committed
oci-image-tool: implement create-runtime-bundle
Fixes opencontainers#99, opencontainers#87 Signed-off-by: Sergiusz Urbaniak <[email protected]>
1 parent 4a68c9d commit 70a918d

File tree

5 files changed

+330
-1
lines changed

5 files changed

+330
-1
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2016 The Linux Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
"log"
20+
"os"
21+
"strings"
22+
23+
"github.com/opencontainers/image-spec/image"
24+
"github.com/spf13/cobra"
25+
)
26+
27+
// supported bundle types
28+
var bundleTypes = []string{
29+
typeImageLayout,
30+
typeImage,
31+
}
32+
33+
type bundleCmd struct {
34+
stdout *log.Logger
35+
stderr *log.Logger
36+
typ string // the type to bundle, can be empty string
37+
ref string
38+
root string
39+
}
40+
41+
func newBundleCmd(stdout, stderr *log.Logger) *cobra.Command {
42+
v := &bundleCmd{
43+
stdout: stdout,
44+
stderr: stderr,
45+
}
46+
47+
cmd := &cobra.Command{
48+
Use: "create-runtime-bundle [src] [dest]",
49+
Short: "Create an OCI image runtime bundle",
50+
Long: `Creates an OCI image runtime bundle at the destination directory [dest] from an OCI image present at [src].`,
51+
Run: v.Run,
52+
}
53+
54+
cmd.Flags().StringVar(
55+
&v.typ, "type", "",
56+
fmt.Sprintf(
57+
`Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "%s"`,
58+
strings.Join(bundleTypes, ","),
59+
),
60+
)
61+
62+
cmd.Flags().StringVar(
63+
&v.ref, "ref", "v1.0",
64+
`The ref pointing to the manifest of the OCI image. This must be present in the "refs" subdirectory of the image.`,
65+
)
66+
67+
cmd.Flags().StringVar(
68+
&v.root, "rootfs", "rootfs",
69+
`A directory representing the root filesystem of the container in the OCI runtime bundle.
70+
It is strongly recommended to keep the default value.`,
71+
)
72+
73+
return cmd
74+
}
75+
76+
func (v *bundleCmd) Run(cmd *cobra.Command, args []string) {
77+
if len(args) != 2 {
78+
v.stderr.Print("both src and dest must be provided")
79+
if err := cmd.Usage(); err != nil {
80+
v.stderr.Println(err)
81+
}
82+
os.Exit(1)
83+
}
84+
85+
if v.typ == "" {
86+
typ, err := autodetect(args[0])
87+
if err != nil {
88+
v.stderr.Printf("%q: autodetection failed: %v", args[0], err)
89+
os.Exit(1)
90+
}
91+
v.typ = typ
92+
}
93+
94+
var err error
95+
switch v.typ {
96+
case typeImageLayout:
97+
err = image.CreateRuntimeBundleLayout(args[0], args[1], v.ref, v.root)
98+
99+
case typeImage:
100+
err = image.CreateRuntimeBundle(args[0], args[1], v.ref, v.root)
101+
}
102+
103+
if err != nil {
104+
v.stderr.Printf("unpacking failed: %v", err)
105+
os.Exit(1)
106+
}
107+
108+
os.Exit(0)
109+
}

cmd/oci-image-tool/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func main() {
3232

3333
cmd.AddCommand(newValidateCmd(stdout, stderr))
3434
cmd.AddCommand(newUnpackCmd(stdout, stderr))
35+
cmd.AddCommand(newBundleCmd(stdout, stderr))
3536

3637
if err := cmd.Execute(); err != nil {
3738
stderr.Println(err)

cmd/oci-image-tool/unpack.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var unpackTypes = []string{
3333
type unpackCmd struct {
3434
stdout *log.Logger
3535
stderr *log.Logger
36-
typ string // the type to validate, can be empty string
36+
typ string // the type to unpack, can be empty string
3737
ref string
3838
}
3939

image/config.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2016 The Linux Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package image
16+
17+
import (
18+
"bytes"
19+
"encoding/json"
20+
"fmt"
21+
"io"
22+
"io/ioutil"
23+
"os"
24+
"path/filepath"
25+
"strconv"
26+
"strings"
27+
28+
"github.com/opencontainers/image-spec/schema"
29+
"github.com/opencontainers/runtime-spec/specs-go"
30+
"github.com/pkg/errors"
31+
)
32+
33+
type cfg struct {
34+
User string
35+
Memory int64
36+
MemorySwap int64
37+
CPUShares int64 `json:"CpuShares"`
38+
ExposedPorts map[string]struct{}
39+
Env []string
40+
Entrypoint []string
41+
Cmd []string
42+
Volumes map[string]struct{}
43+
WorkingDir string
44+
}
45+
46+
type config struct {
47+
Architecture string `json:"architecture"`
48+
OS string `json:"os"`
49+
Config cfg `json:"config"`
50+
}
51+
52+
func findConfig(w walker, d *descriptor) (*config, error) {
53+
var c config
54+
cpath := filepath.Join("blobs", d.Digest)
55+
56+
f := func(path string, info os.FileInfo, r io.Reader) error {
57+
if info.IsDir() {
58+
return nil
59+
}
60+
61+
if filepath.Clean(path) != cpath {
62+
return nil
63+
}
64+
65+
buf, err := ioutil.ReadAll(r)
66+
if err != nil {
67+
return errors.Wrapf(err, "%s: error reading config", path)
68+
}
69+
70+
if err := schema.MediaTypeImageSerializationConfig.Validate(bytes.NewReader(buf)); err != nil {
71+
return errors.Wrapf(err, "%s: config validation failed", path)
72+
}
73+
74+
if err := json.Unmarshal(buf, &c); err != nil {
75+
return err
76+
}
77+
78+
return errEOW
79+
}
80+
81+
switch err := w.walk(f); err {
82+
case nil:
83+
return nil, fmt.Errorf("%s: config not found", cpath)
84+
case errEOW:
85+
// found, continue below
86+
default:
87+
return nil, err
88+
}
89+
90+
return &c, nil
91+
}
92+
93+
func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) {
94+
var s specs.Spec
95+
s.Version = "0.5.0"
96+
s.Root.Path = rootfs
97+
s.Process.Cwd = c.Config.WorkingDir
98+
s.Process.Env = append([]string(nil), c.Config.Env...)
99+
s.Process.Args = append([]string(nil), c.Config.Entrypoint...)
100+
s.Process.Args = append(s.Process.Args, c.Config.Cmd...)
101+
102+
if uid, err := strconv.Atoi(c.Config.User); err == nil {
103+
s.Process.User.UID = uint32(uid)
104+
} else if ug := strings.Split(c.Config.User, ":"); len(ug) == 2 {
105+
uid, err := strconv.Atoi(ug[0])
106+
if err != nil {
107+
return nil, errors.New("config.User: unsupported uid format")
108+
}
109+
110+
gid, err := strconv.Atoi(ug[1])
111+
if err != nil {
112+
return nil, errors.New("config.User: unsupported gid format")
113+
}
114+
115+
s.Process.User.UID = uint32(uid)
116+
s.Process.User.GID = uint32(gid)
117+
} else {
118+
return nil, errors.New("config.User: unsupported format")
119+
}
120+
121+
s.Platform.OS = c.OS
122+
s.Platform.Arch = c.Architecture
123+
124+
if c.OS == "linux" {
125+
mem := uint64(c.Config.Memory)
126+
swap := uint64(c.Config.MemorySwap)
127+
shares := uint64(c.Config.CPUShares)
128+
129+
s.Linux.Resources = &specs.Resources{
130+
CPU: &specs.CPU{
131+
Shares: &shares,
132+
},
133+
134+
Memory: &specs.Memory{
135+
Limit: &mem,
136+
Reservation: &mem,
137+
Swap: &swap,
138+
},
139+
}
140+
}
141+
142+
for vol := range c.Config.Volumes {
143+
s.Mounts = append(
144+
s.Mounts,
145+
specs.Mount{
146+
Destination: vol,
147+
Type: "bind",
148+
Options: []string{"rbind"},
149+
},
150+
)
151+
}
152+
153+
return &s, nil
154+
}

image/image.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
package image
1616

1717
import (
18+
"encoding/json"
1819
"os"
20+
"path/filepath"
1921

2022
"github.com/pkg/errors"
2123
)
@@ -101,3 +103,66 @@ func unpack(w walker, dest, refName string) error {
101103

102104
return m.unpack(w, dest)
103105
}
106+
107+
// CreateRuntimeBundleLayout walks through the file tree given given by src and
108+
// creates an OCI runtime bundle in the given destination dest
109+
// or returns an error if the unpacking failed.
110+
func CreateRuntimeBundleLayout(src, dest, ref, root string) error {
111+
return createRuntimeBundle(newPathWalker(src), dest, ref, root)
112+
}
113+
114+
// CreateRuntimeBundle walks through the given .tar file and
115+
// creates an OCI runtime bundle in the given destination dest
116+
// or returns an error if the unpacking failed.
117+
func CreateRuntimeBundle(tarFile, dest, ref, root string) error {
118+
f, err := os.Open(tarFile)
119+
if err != nil {
120+
return errors.Wrap(err, "unable to open file")
121+
}
122+
defer f.Close()
123+
124+
return createRuntimeBundle(newTarWalker(f), dest, ref, root)
125+
}
126+
127+
func createRuntimeBundle(w walker, dest, refName, rootfs string) error {
128+
ref, err := findDescriptor(w, refName)
129+
if err != nil {
130+
return err
131+
}
132+
133+
if err = ref.validate(w); err != nil {
134+
return err
135+
}
136+
137+
m, err := findManifest(w, ref)
138+
if err != nil {
139+
return err
140+
}
141+
142+
if err = m.validate(w); err != nil {
143+
return err
144+
}
145+
146+
c, err := findConfig(w, &m.Config)
147+
if err != nil {
148+
return err
149+
}
150+
151+
err = m.unpack(w, filepath.Join(dest, rootfs))
152+
if err != nil {
153+
return err
154+
}
155+
156+
spec, err := c.runtimeSpec(rootfs)
157+
if err != nil {
158+
return err
159+
}
160+
161+
f, err := os.Create(filepath.Join(dest, "config.json"))
162+
if err != nil {
163+
return err
164+
}
165+
defer f.Close()
166+
167+
return json.NewEncoder(f).Encode(spec)
168+
}

0 commit comments

Comments
 (0)