-
Notifications
You must be signed in to change notification settings - Fork 774
oci-image-tool: implement create-runtime-bundle #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 bundle types | ||
| var bundleTypes = []string{ | ||
| typeImageLayout, | ||
| typeImage, | ||
| } | ||
|
|
||
| type bundleCmd struct { | ||
| stdout *log.Logger | ||
| stderr *log.Logger | ||
| typ string // the type to bundle, 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) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| // 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) { | ||
| if c.OS != "linux" { | ||
| return nil, fmt.Errorf("%s: unsupported OS", c.OS) | ||
| } | ||
|
|
||
| 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") | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These should probably be throwing “not supported yet” errors if it can't make the conversion until you add code to spin up a container with all the mounts and use getent or similar (see opencontainers/runtime-spec#38). Docker may be doing something like this already, so maybe you can grab their code. And this is hideous, ugly, and dangerous, which is why the runtime spec punted it to config authors.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ack, I'll return an error in this case, thanks for the suggestion 👍 |
||
|
|
||
| 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 | ||
|
|
||
| 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 | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're currently not translating this to anything (because the runtime spec doesn't have knobs for Linux networking). @philips, can you explain how you expect higher-levels notice and apply this configuration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But i'm sure that other extentsions will come along and use such. Also, having a golang structure for the data will allow folks to import and use it for their own use cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Thu, Jun 02, 2016 at 11:49:50AM -0700, Vincent Batts wrote:
How do they find it? If this command unpacks $IMAGE into $BUNDLE, but
does not leave a pointer in $BUNDLE to say “this was unpacked from
$IMAGE”, I don't see how tooling invoked on the $BUNDLE would be able
to find this image-specific data. So to support that additional
tooling, you either need:
a. A generic “this was unpacked from $IMAGE” reference in the bundle
directory somewhere, or
b. To copy all of the “unused” information like ExposedPorts over to
some well-known location inside the bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vbatts the
cfgstructure currently is private; here inimage-specwe don't have redundantly declared public Golang structs, only the JSON schema files. If we want to go the route as https://github.com/opencontainers/runtime-spec/tree/master/specs-go, I'd have to refactor; please advise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wking commented on #87 about networking.