Skip to content

Commit 5a38e9f

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 5a38e9f

File tree

4 files changed

+310
-0
lines changed

4 files changed

+310
-0
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 unpack 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 validate, 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)

image/config.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 {
94+
var s specs.Spec
95+
s.Version = "v0.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+
}
105+
106+
if ug := strings.Split(c.Config.User, ":"); len(ug) == 2 {
107+
if uid, err := strconv.Atoi(ug[0]); err == nil {
108+
s.Process.User.UID = uint32(uid)
109+
}
110+
111+
if gid, err := strconv.Atoi(ug[1]); err == nil {
112+
s.Process.User.GID = uint32(gid)
113+
}
114+
}
115+
116+
s.Platform.OS = c.OS
117+
s.Platform.Arch = c.Architecture
118+
119+
if c.OS == "linux" {
120+
mem := uint64(c.Config.Memory)
121+
swap := uint64(c.Config.MemorySwap)
122+
shares := uint64(c.Config.CPUShares)
123+
124+
s.Linux.Resources = &specs.Resources{
125+
CPU: &specs.CPU{
126+
Shares: &shares,
127+
},
128+
129+
Memory: &specs.Memory{
130+
Limit: &mem,
131+
Reservation: &mem,
132+
Swap: &swap,
133+
},
134+
}
135+
}
136+
137+
return s
138+
}

image/image.go

Lines changed: 62 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,63 @@ 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 := c.runtimeSpec(rootfs)
157+
158+
f, err := os.Create(filepath.Join(dest, "config.json"))
159+
if err != nil {
160+
return err
161+
}
162+
defer f.Close()
163+
164+
return json.NewEncoder(f).Encode(spec)
165+
}

0 commit comments

Comments
 (0)