Skip to content

Commit

Permalink
oci-image-tool: implement create-runtime-bundle
Browse files Browse the repository at this point in the history
Fixes #99, #87

Signed-off-by: Sergiusz Urbaniak <[email protected]>
  • Loading branch information
Sergiusz Urbaniak authored and s-urbaniak committed Jun 3, 2016
1 parent 4a68c9d commit cfd87a7
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 0 deletions.
109 changes: 109 additions & 0 deletions cmd/oci-image-tool/create_runtime_bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2016 The Linux Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"fmt"
"log"
"os"
"strings"

"github.com/opencontainers/image-spec/image"
"github.com/spf13/cobra"
)

// supported unpack types
var bundleTypes = []string{
typeImageLayout,
typeImage,
}

type bundleCmd struct {
stdout *log.Logger
stderr *log.Logger
typ string // the type to validate, can be empty string
ref string
root string
}

func newBundleCmd(stdout, stderr *log.Logger) *cobra.Command {
v := &bundleCmd{
stdout: stdout,
stderr: stderr,
}

cmd := &cobra.Command{
Use: "create-runtime-bundle [src] [dest]",
Short: "Create an OCI image runtime bundle",
Long: `Creates an OCI image runtime bundle at the destination directory [dest] from an OCI image present at [src].`,
Run: v.Run,
}

cmd.Flags().StringVar(
&v.typ, "type", "",
fmt.Sprintf(
`Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "%s"`,
strings.Join(bundleTypes, ","),
),
)

cmd.Flags().StringVar(
&v.ref, "ref", "v1.0",
`The ref pointing to the manifest of the OCI image. This must be present in the "refs" subdirectory of the image.`,
)

cmd.Flags().StringVar(
&v.root, "rootfs", "rootfs",
`A directory representing the root filesystem of the container in the OCI runtime bundle.
It is strongly recommended to keep the default value.`,
)

return cmd
}

func (v *bundleCmd) Run(cmd *cobra.Command, args []string) {
if len(args) != 2 {
v.stderr.Print("both src and dest must be provided")
if err := cmd.Usage(); err != nil {
v.stderr.Println(err)
}
os.Exit(1)
}

if v.typ == "" {
typ, err := autodetect(args[0])
if err != nil {
v.stderr.Printf("%q: autodetection failed: %v", args[0], err)
os.Exit(1)
}
v.typ = typ
}

var err error
switch v.typ {
case typeImageLayout:
err = image.CreateRuntimeBundleLayout(args[0], args[1], v.ref, v.root)

case typeImage:
err = image.CreateRuntimeBundle(args[0], args[1], v.ref, v.root)
}

if err != nil {
v.stderr.Printf("unpacking failed: %v", err)
os.Exit(1)
}

os.Exit(0)
}
1 change: 1 addition & 0 deletions cmd/oci-image-tool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func main() {

cmd.AddCommand(newValidateCmd(stdout, stderr))
cmd.AddCommand(newUnpackCmd(stdout, stderr))
cmd.AddCommand(newBundleCmd(stdout, stderr))

if err := cmd.Execute(); err != nil {
stderr.Println(err)
Expand Down
154 changes: 154 additions & 0 deletions image/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright 2016 The Linux Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package image

import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/opencontainers/image-spec/schema"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)

type cfg struct {
User string
Memory int64
MemorySwap int64
CPUShares int64 `json:"CpuShares"`
ExposedPorts map[string]struct{}
Env []string
Entrypoint []string
Cmd []string
Volumes map[string]struct{}
WorkingDir string
}

type config struct {
Architecture string `json:"architecture"`
OS string `json:"os"`
Config cfg `json:"config"`
}

func findConfig(w walker, d *descriptor) (*config, error) {
var c config
cpath := filepath.Join("blobs", d.Digest)

f := func(path string, info os.FileInfo, r io.Reader) error {
if info.IsDir() {
return nil
}

if filepath.Clean(path) != cpath {
return nil
}

buf, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrapf(err, "%s: error reading config", path)
}

if err := schema.MediaTypeImageSerializationConfig.Validate(bytes.NewReader(buf)); err != nil {
return errors.Wrapf(err, "%s: config validation failed", path)
}

if err := json.Unmarshal(buf, &c); err != nil {
return err
}

return errEOW
}

switch err := w.walk(f); err {
case nil:
return nil, fmt.Errorf("%s: config not found", cpath)
case errEOW:
// found, continue below
default:
return nil, err
}

return &c, nil
}

func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) {
var s specs.Spec
s.Version = "0.5.0"
s.Root.Path = rootfs
s.Process.Cwd = c.Config.WorkingDir
s.Process.Env = append([]string(nil), c.Config.Env...)
s.Process.Args = append([]string(nil), c.Config.Entrypoint...)
s.Process.Args = append(s.Process.Args, c.Config.Cmd...)

if uid, err := strconv.Atoi(c.Config.User); err == nil {
s.Process.User.UID = uint32(uid)
} else if ug := strings.Split(c.Config.User, ":"); len(ug) == 2 {
uid, err := strconv.Atoi(ug[0])
if err != nil {
return nil, errors.New("config.User: unsupported uid format")
}

gid, err := strconv.Atoi(ug[1])
if err != nil {
return nil, errors.New("config.User: unsupported gid format")
}

s.Process.User.UID = uint32(uid)
s.Process.User.GID = uint32(gid)
} else {
return nil, errors.New("config.User: unsupported format")
}

s.Platform.OS = c.OS
s.Platform.Arch = c.Architecture

if c.OS == "linux" {
mem := uint64(c.Config.Memory)
swap := uint64(c.Config.MemorySwap)
shares := uint64(c.Config.CPUShares)

s.Linux.Resources = &specs.Resources{
CPU: &specs.CPU{
Shares: &shares,
},

Memory: &specs.Memory{
Limit: &mem,
Reservation: &mem,
Swap: &swap,
},
}
}

for vol := range c.Config.Volumes {
s.Mounts = append(
s.Mounts,
specs.Mount{
Destination: vol,
Type: "bind",
Options: []string{"rbind"},
},
)
}

return &s, nil
}
65 changes: 65 additions & 0 deletions image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package image

import (
"encoding/json"
"os"
"path/filepath"

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

return m.unpack(w, dest)
}

// CreateRuntimeBundleLayout walks through the file tree given given by src and
// creates an OCI runtime bundle in the given destination dest
// or returns an error if the unpacking failed.
func CreateRuntimeBundleLayout(src, dest, ref, root string) error {
return createRuntimeBundle(newPathWalker(src), dest, ref, root)
}

// CreateRuntimeBundle walks through the given .tar file and
// creates an OCI runtime bundle in the given destination dest
// or returns an error if the unpacking failed.
func CreateRuntimeBundle(tarFile, dest, ref, root string) error {
f, err := os.Open(tarFile)
if err != nil {
return errors.Wrap(err, "unable to open file")
}
defer f.Close()

return createRuntimeBundle(newTarWalker(f), dest, ref, root)
}

func createRuntimeBundle(w walker, dest, refName, rootfs string) error {
ref, err := findDescriptor(w, refName)
if err != nil {
return err
}

if err = ref.validate(w); err != nil {
return err
}

m, err := findManifest(w, ref)
if err != nil {
return err
}

if err = m.validate(w); err != nil {
return err
}

c, err := findConfig(w, &m.Config)
if err != nil {
return err
}

err = m.unpack(w, filepath.Join(dest, rootfs))
if err != nil {
return err
}

spec, err := c.runtimeSpec(rootfs)
if err != nil {
return err
}

f, err := os.Create(filepath.Join(dest, "config.json"))
if err != nil {
return err
}
defer f.Close()

return json.NewEncoder(f).Encode(spec)
}

0 comments on commit cfd87a7

Please sign in to comment.