Skip to content

Commit

Permalink
layout: unpacking initial commit
Browse files Browse the repository at this point in the history
Fixes partially #75

Signed-off-by: Sergiusz Urbaniak <sur@coreos.com>
  • Loading branch information
s-urbaniak authored and Sergiusz Urbaniak committed May 22, 2016
1 parent 995ae0b commit 4aa9e1f
Show file tree
Hide file tree
Showing 8 changed files with 561 additions and 82 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
code-of-conduct.md
oci-image-tool
oci-validate-examples
/oci-image-tool
/oci-validate-examples
output
103 changes: 103 additions & 0 deletions cmd/oci-image-tool/autodetect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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 (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"os"

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

// supported autodetection types
const (
typeImageLayout = "imageLayout"
typeImage = "image"
typeManifest = "manifest"
typeManifestList = "manifestList"
typeConfig = "config"
)

// autodetect detects the validation type for the given path
// or an error if the validation type could not be resolved.
func autodetect(path string) (string, error) {
fi, err := os.Stat(path)
if err != nil {
return "", errors.Wrapf(err, "unable to access path") // err from os.Stat includes path name
}

if fi.IsDir() {
return typeImageLayout, nil
}

f, err := os.Open(path)
if err != nil {
return "", errors.Wrap(err, "unable to open file") // os.Open includes the filename
}
defer f.Close()

buf, err := ioutil.ReadAll(io.LimitReader(f, 512)) // read some initial bytes to detect content
if err != nil {
return "", errors.Wrap(err, "unable to read")
}

mimeType := http.DetectContentType(buf)

switch mimeType {
case "application/x-gzip":
return typeImage, nil

case "application/octet-stream":
return typeImage, nil

case "text/plain; charset=utf-8":
// might be a JSON file, will be handled below

default:
return "", errors.New("unknown file type")
}

if _, err := f.Seek(0, os.SEEK_SET); err != nil {
return "", errors.Wrap(err, "unable to seek")
}

header := struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Config interface{} `json:"config"`
}{}

if err := json.NewDecoder(f).Decode(&header); err != nil {
return "", errors.Wrap(err, "unable to parse JSON")
}

switch {
case header.MediaType == string(schema.MediaTypeManifest):
return typeManifest, nil

case header.MediaType == string(schema.MediaTypeManifestList):
return typeManifestList, nil

case header.MediaType == "" && header.SchemaVersion == 0 && header.Config != nil:
// config files don't have mediaType/schemaVersion header
return typeConfig, nil
}

return "", errors.New("unknown media type")
}
2 changes: 2 additions & 0 deletions cmd/oci-image-tool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func main() {
stderr := log.New(os.Stderr, "", 0)

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

if err := cmd.Execute(); err != nil {
stderr.Println(err)
os.Exit(1)
Expand Down
101 changes: 101 additions & 0 deletions cmd/oci-image-tool/unpack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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 unpackTypes = []string{
typeImageLayout,
typeImage,
}

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

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

cmd := &cobra.Command{
Use: "unpack [src] [dest]",
Short: "Unpack an image or image source layout",
Long: `Unpack the OCI image .tar file or OCI image layout directory present at [src] to the destination directory [dest].`,
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(unpackTypes, ","),
),
)

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

return cmd
}

func (v *unpackCmd) 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.UnpackLayout(args[0], args[1], v.manifest)
case typeImage:
err = image.Unpack(args[0], args[1], v.manifest)
}

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

os.Exit(0)
}
80 changes: 0 additions & 80 deletions cmd/oci-image-tool/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@
package main

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"

Expand All @@ -30,14 +26,6 @@ import (
)

// supported validation types
const (
typeImageLayout = "imageLayout"
typeImage = "image"
typeManifest = "manifest"
typeManifestList = "manifestList"
typeConfig = "config"
)

var validateTypes = []string{
typeImageLayout,
typeImage,
Expand Down Expand Up @@ -145,71 +133,3 @@ func (v *validateCmd) validatePath(name string) error {

return fmt.Errorf("type %q unimplemented", typ)
}

// autodetect detects the validation type for the given path
// or an error if the validation type could not be resolved.
func autodetect(path string) (string, error) {
fi, err := os.Stat(path)
if err != nil {
return "", errors.Wrapf(err, "unable to access path") // err from os.Stat includes path name
}

if fi.IsDir() {
return typeImageLayout, nil
}

f, err := os.Open(path)
if err != nil {
return "", errors.Wrap(err, "unable to open file") // os.Open includes the filename
}
defer f.Close()

buf, err := ioutil.ReadAll(io.LimitReader(f, 512)) // read some initial bytes to detect content
if err != nil {
return "", errors.Wrap(err, "unable to read")
}

mimeType := http.DetectContentType(buf)

switch mimeType {
case "application/x-gzip":
return typeImage, nil

case "application/octet-stream":
return typeImage, nil

case "text/plain; charset=utf-8":
// might be a JSON file, will be handled below

default:
return "", errors.New("unknown file type")
}

if _, err := f.Seek(0, os.SEEK_SET); err != nil {
return "", errors.Wrap(err, "unable to seek")
}

header := struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Config interface{} `json:"config"`
}{}

if err := json.NewDecoder(f).Decode(&header); err != nil {
return "", errors.Wrap(err, "unable to parse JSON")
}

switch {
case header.MediaType == string(schema.MediaTypeManifest):
return typeManifest, nil

case header.MediaType == string(schema.MediaTypeManifestList):
return typeManifestList, nil

case header.MediaType == "" && header.SchemaVersion == 0 && header.Config != nil:
// config files don't have mediaType/schemaVersion header
return typeConfig, nil
}

return "", errors.New("unknown media type")
}
16 changes: 16 additions & 0 deletions image/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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 defines methods for validating, and unpacking OCI images.
package image
Loading

0 comments on commit 4aa9e1f

Please sign in to comment.