Initial version, needs work
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
parent
8b7705022a
commit
03b2e90dba
|
@ -0,0 +1,96 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/image/copy"
|
||||||
|
"github.com/containers/image/signature"
|
||||||
|
"github.com/containers/image/transports"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commitFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "name of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "root",
|
||||||
|
Usage: "root directory of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "link",
|
||||||
|
Usage: "symlink to the root directory of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Usage: "image to create",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func commitCmd(c *cli.Context) error {
|
||||||
|
store, err := getStore(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
if c.IsSet("name") {
|
||||||
|
name = c.String("name")
|
||||||
|
}
|
||||||
|
root := ""
|
||||||
|
if c.IsSet("root") {
|
||||||
|
root = c.String("root")
|
||||||
|
}
|
||||||
|
link := ""
|
||||||
|
if c.IsSet("link") {
|
||||||
|
link = c.String("link")
|
||||||
|
}
|
||||||
|
output := ""
|
||||||
|
if c.IsSet("output") {
|
||||||
|
output = c.String("output")
|
||||||
|
}
|
||||||
|
if output == "" {
|
||||||
|
return fmt.Errorf("the --output flag must be specified")
|
||||||
|
}
|
||||||
|
if name == "" && root == "" && link == "" {
|
||||||
|
return fmt.Errorf("either --name or --root or --link, or some combination, must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := lookupContainer(store, name, root, link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mdata, err := store.GetMetadata(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
metadata := ContainerMetadata{}
|
||||||
|
err = json.Unmarshal([]byte(mdata), &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := signature.DefaultPolicy(getSystemContext(c))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
policyContext, err := signature.NewPolicyContext(policy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
destRef, err := transports.ParseImageName(output)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing output image name %q: %v", output, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := updateConfig(c, metadata.Config)
|
||||||
|
err = copy.Image(policyContext, destRef, makeContainerImageRef(store, container, string(config)), getCopyOptions())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/image/copy"
|
||||||
|
is "github.com/containers/image/storage"
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
"github.com/containers/storage/storage"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContainerType = "stevedore 0.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContainerMetadata struct {
|
||||||
|
Type string
|
||||||
|
Config []byte
|
||||||
|
Manifest []byte
|
||||||
|
Links []string
|
||||||
|
Mounts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStore(c *cli.Context) (storage.Store, error) {
|
||||||
|
options := storage.DefaultStoreOptions
|
||||||
|
if c.GlobalIsSet("root") || c.GlobalIsSet("runroot") {
|
||||||
|
options.GraphRoot = c.GlobalString("root")
|
||||||
|
options.RunRoot = c.GlobalString("runroot")
|
||||||
|
}
|
||||||
|
if c.GlobalIsSet("storage-driver") {
|
||||||
|
options.GraphDriverName = c.GlobalString("storage-driver")
|
||||||
|
}
|
||||||
|
if c.GlobalIsSet("storage-options") {
|
||||||
|
opts := c.GlobalStringSlice("storage-options")
|
||||||
|
if len(opts) > 0 {
|
||||||
|
options.GraphDriverOptions = opts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store, err := storage.GetStore(options)
|
||||||
|
if store != nil {
|
||||||
|
is.Transport.SetStore(store)
|
||||||
|
}
|
||||||
|
return store, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSystemContext(c *cli.Context) *types.SystemContext {
|
||||||
|
sc := &types.SystemContext{}
|
||||||
|
if c.GlobalIsSet("signature-policy") {
|
||||||
|
sc.SignaturePolicyPath = c.GlobalString("signature-policy")
|
||||||
|
}
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCopyOptions() *copy.Options {
|
||||||
|
return ©.Options{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupContainer(store storage.Store, name, root, link string) (*storage.Container, error) {
|
||||||
|
containers, err := store.Containers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error listing containers: %v", err)
|
||||||
|
}
|
||||||
|
for _, c := range containers {
|
||||||
|
if name != "" {
|
||||||
|
matches := false
|
||||||
|
for _, n := range c.Names {
|
||||||
|
if name == n {
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matches {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata := ContainerMetadata{}
|
||||||
|
if root != "" || link != "" {
|
||||||
|
mdata, err := store.GetMetadata(c.ID)
|
||||||
|
if err != nil || mdata == "" {
|
||||||
|
// probably not one of ours
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(mdata), &metadata)
|
||||||
|
if err != nil {
|
||||||
|
// probably not one of ours
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if root != "" {
|
||||||
|
matches := false
|
||||||
|
for _, m := range metadata.Mounts {
|
||||||
|
if m == root {
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matches {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if link != "" {
|
||||||
|
matches := false
|
||||||
|
for _, l := range metadata.Links {
|
||||||
|
if l == link {
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matches {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no matching container found")
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/opencontainers/runtime-tools/generate"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configFlags = []cli.Flag{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateConfig(c *cli.Context, config []byte) []byte {
|
||||||
|
buffer := bytes.Buffer{}
|
||||||
|
g, err := generate.NewFromTemplate(bytes.NewReader(config))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error importing template configuration, using original configuration")
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
options := generate.ExportOptions{}
|
||||||
|
err = g.Save(buffer, options)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error exporting updated configuration, using original configuration")
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
return buffer.Bytes()
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
deleteFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "name of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "root",
|
||||||
|
Usage: "root directory of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "link",
|
||||||
|
Usage: "symlink to the root directory of the working container",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func deleteCmd(c *cli.Context) error {
|
||||||
|
store, err := getStore(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
if c.IsSet("name") {
|
||||||
|
name = c.String("name")
|
||||||
|
}
|
||||||
|
root := ""
|
||||||
|
if c.IsSet("root") {
|
||||||
|
root = c.String("root")
|
||||||
|
}
|
||||||
|
link := ""
|
||||||
|
if c.IsSet("link") {
|
||||||
|
link = c.String("link")
|
||||||
|
}
|
||||||
|
if name == "" && root == "" && link == "" {
|
||||||
|
return fmt.Errorf("either --name or --root or --link, or some combination, must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := lookupContainer(store, name, root, link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mdata, err := store.GetMetadata(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
metadata := ContainerMetadata{}
|
||||||
|
err = json.Unmarshal([]byte(mdata), &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, link := range metadata.Links {
|
||||||
|
err = os.Remove(link)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error removing symlink %q: %v", link, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata.Links = nil
|
||||||
|
|
||||||
|
mdata2, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = store.SetMetadata(container.ID, string(mdata2))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.DeleteContainer(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containers/image/copy"
|
||||||
|
"github.com/containers/image/signature"
|
||||||
|
is "github.com/containers/image/storage"
|
||||||
|
"github.com/containers/image/transports"
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
"github.com/containers/storage/storage"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fromFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "set a name for the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "image",
|
||||||
|
Usage: "name of the starting image",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "pull",
|
||||||
|
Usage: "pull the image if not present",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "registry",
|
||||||
|
Usage: "prefix to prepend to the image name in order to pull the image",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "mount",
|
||||||
|
Usage: "mount the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "link",
|
||||||
|
Usage: "name of a symlink to create to the root directory of the container",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func pullImage(c *cli.Context, store storage.Store, sc *types.SystemContext, name string) error {
|
||||||
|
spec := name
|
||||||
|
if c.IsSet("registry") {
|
||||||
|
spec = c.String("registry") + name
|
||||||
|
}
|
||||||
|
|
||||||
|
srcRef, err := transports.ParseImageName(spec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing image name %q: %v", spec, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref := srcRef.DockerReference(); ref != nil {
|
||||||
|
name = ref.FullName()
|
||||||
|
}
|
||||||
|
|
||||||
|
destRef, err := is.Transport.ParseStoreReference(store, name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing full image name %q: %v", spec, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := signature.DefaultPolicy(getSystemContext(c))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
policyContext, err := signature.NewPolicyContext(policy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("copying %q to %q", spec, name)
|
||||||
|
|
||||||
|
err = copy.Image(policyContext, destRef, srcRef, getCopyOptions())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go find the image, and attach the requested name to it, so that we
|
||||||
|
// can more easily find it later, even if the destination reference
|
||||||
|
// looks different.
|
||||||
|
destImage, err := is.Transport.GetStoreImage(store, destRef)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
names := append(destImage.Names, spec, name)
|
||||||
|
err = store.SetNames(destImage.ID, names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromCmd(c *cli.Context) error {
|
||||||
|
var img *storage.Image
|
||||||
|
manifest := []byte{}
|
||||||
|
config := []byte{}
|
||||||
|
|
||||||
|
store, err := getStore(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
image := ""
|
||||||
|
if c.IsSet("image") {
|
||||||
|
image = c.String("image")
|
||||||
|
}
|
||||||
|
|
||||||
|
pull := false
|
||||||
|
if c.IsSet("pull") {
|
||||||
|
pull = c.Bool("pull")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := "working-container"
|
||||||
|
if c.IsSet("name") {
|
||||||
|
name = c.String("name")
|
||||||
|
} else {
|
||||||
|
if image != "" {
|
||||||
|
name = image + "-working-container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
suffix := 1
|
||||||
|
tmpName := name
|
||||||
|
err = nil
|
||||||
|
for err != storage.ErrContainerUnknown {
|
||||||
|
_, err = store.GetContainer(tmpName)
|
||||||
|
if err == nil {
|
||||||
|
suffix++
|
||||||
|
tmpName = fmt.Sprintf("%s-%d", name, suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = tmpName
|
||||||
|
}
|
||||||
|
|
||||||
|
mount := false
|
||||||
|
if c.IsSet("mount") {
|
||||||
|
mount = c.Bool("mount")
|
||||||
|
}
|
||||||
|
|
||||||
|
link := ""
|
||||||
|
if c.IsSet("link") {
|
||||||
|
link = c.String("link")
|
||||||
|
if link == "" {
|
||||||
|
return fmt.Errorf("link location can not be empty")
|
||||||
|
}
|
||||||
|
abs, err := filepath.Abs(link)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting link path %q to absolute path: %v", link, err)
|
||||||
|
}
|
||||||
|
link = abs
|
||||||
|
}
|
||||||
|
|
||||||
|
systemContext := getSystemContext(c)
|
||||||
|
|
||||||
|
if image != "" {
|
||||||
|
ref, err := is.Transport.ParseStoreReference(store, image)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing reference to image %q: %v", image, err)
|
||||||
|
}
|
||||||
|
img, err = is.Transport.GetStoreImage(store, ref)
|
||||||
|
if err != nil {
|
||||||
|
if err != storage.ErrImageUnknown || !pull {
|
||||||
|
return fmt.Errorf("no such image %q: %v", image, err)
|
||||||
|
}
|
||||||
|
err = pullImage(c, store, systemContext, image)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error pulling image %q: %v", image, err)
|
||||||
|
}
|
||||||
|
ref, err = is.Transport.ParseStoreReference(store, image)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing reference to image %q: %v", image, err)
|
||||||
|
}
|
||||||
|
img, err = is.Transport.GetStoreImage(store, ref)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("no such image %q: %v", image, err)
|
||||||
|
}
|
||||||
|
image = img.ID
|
||||||
|
src, err := ref.NewImage(systemContext)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error instantiating image: %v", err)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
config, err = src.ConfigBlob()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading image configuration: %v", err)
|
||||||
|
}
|
||||||
|
manifest, _, err = src.Manifest()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading image manifest: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := &ContainerMetadata{
|
||||||
|
Type: ContainerType,
|
||||||
|
Config: config,
|
||||||
|
Manifest: manifest,
|
||||||
|
}
|
||||||
|
|
||||||
|
options := storage.ContainerOptions{}
|
||||||
|
container, err := store.CreateContainer("", []string{name}, image, "", "", &options)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mount {
|
||||||
|
mountPoint, err := store.Mount(container.ID, "")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error mounting container: %v", err)
|
||||||
|
}
|
||||||
|
metadata.Mounts = []string{mountPoint}
|
||||||
|
if link != "" {
|
||||||
|
err = os.Symlink(mountPoint, link)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating symlink to %q: %v", mountPoint, err)
|
||||||
|
}
|
||||||
|
metadata.Links = []string{link}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", name)
|
||||||
|
|
||||||
|
mdata, err := json.Marshal(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error encoding container metadata: %v", err)
|
||||||
|
}
|
||||||
|
err = store.SetMetadata(container.ID, string(mdata))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error saving container metadata: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,252 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containers/image/docker/reference"
|
||||||
|
"github.com/containers/image/image"
|
||||||
|
is "github.com/containers/image/storage"
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
"github.com/containers/storage/pkg/archive"
|
||||||
|
"github.com/containers/storage/pkg/ioutils"
|
||||||
|
"github.com/containers/storage/storage"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go"
|
||||||
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type containerImageRef struct {
|
||||||
|
store storage.Store
|
||||||
|
container *storage.Container
|
||||||
|
name reference.Named
|
||||||
|
config []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerImageSource struct {
|
||||||
|
path string
|
||||||
|
ref *containerImageRef
|
||||||
|
store storage.Store
|
||||||
|
container *storage.Container
|
||||||
|
config []byte
|
||||||
|
configDigest digest.Digest
|
||||||
|
manifest []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) NewImage(sc *types.SystemContext) (types.Image, error) {
|
||||||
|
src, err := i.NewImageSource(sc, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return image.FromSource(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) NewImageSource(sc *types.SystemContext, manifestTypes []string) (src types.ImageSource, err error) {
|
||||||
|
if len(manifestTypes) > 0 {
|
||||||
|
ok := false
|
||||||
|
for _, mt := range manifestTypes {
|
||||||
|
if mt == v1.MediaTypeImageManifest {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no supported manifest types")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layers := []string{}
|
||||||
|
layerID := i.container.LayerID
|
||||||
|
layer, err := i.store.GetLayer(layerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read layer %q: %v", layerID, err)
|
||||||
|
}
|
||||||
|
for layer != nil {
|
||||||
|
layers = append(append([]string{}, layerID), layers...)
|
||||||
|
layerID = layer.Parent
|
||||||
|
if layerID == "" {
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
layer, err = i.store.GetLayer(layerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read layer %q: %v", layerID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := ioutil.TempDir(os.TempDir(), "stevedore")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if src == nil {
|
||||||
|
err2 := os.RemoveAll(path)
|
||||||
|
if err2 != nil {
|
||||||
|
logrus.Errorf("error removing %q: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
manifest := v1.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: v1.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageConfig,
|
||||||
|
Digest: digest.FromBytes(i.config),
|
||||||
|
Size: int64(len(i.config)),
|
||||||
|
},
|
||||||
|
Layers: []v1.Descriptor{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, layerID := range layers {
|
||||||
|
rc, err := i.store.Diff("", layerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error extracting layer %q: %v", layerID, err)
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
uncompressed, err := archive.DecompressStream(rc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decompressing layer %q: %v", layerID, err)
|
||||||
|
}
|
||||||
|
defer uncompressed.Close()
|
||||||
|
hasher := digest.Canonical.Digester()
|
||||||
|
reader := io.TeeReader(uncompressed, hasher.Hash())
|
||||||
|
layerFile, err := os.OpenFile(filepath.Join(path, "layer"), os.O_CREATE|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error opening file for layer %q: %v", layerID, err)
|
||||||
|
}
|
||||||
|
size, err := io.Copy(layerFile, reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error storing layer %q to file: %v", layerID, err)
|
||||||
|
}
|
||||||
|
layerFile.Close()
|
||||||
|
err = os.Rename(filepath.Join(path, "layer"), filepath.Join(path, hasher.Digest().String()))
|
||||||
|
layerDescriptor := v1.Descriptor{
|
||||||
|
MediaType: v1.MediaTypeImageLayer,
|
||||||
|
Digest: hasher.Digest(),
|
||||||
|
Size: size,
|
||||||
|
}
|
||||||
|
manifest.Layers = append(manifest.Layers, layerDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
mfest, err := json.Marshal(&manifest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("manifest = %s\n", mfest)
|
||||||
|
|
||||||
|
src = &containerImageSource{
|
||||||
|
path: path,
|
||||||
|
ref: i,
|
||||||
|
store: i.store,
|
||||||
|
container: i.container,
|
||||||
|
manifest: mfest,
|
||||||
|
config: i.config,
|
||||||
|
configDigest: manifest.Config.Digest,
|
||||||
|
}
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) NewImageDestination(sc *types.SystemContext) (types.ImageDestination, error) {
|
||||||
|
return nil, fmt.Errorf("can't write to a container")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) DockerReference() reference.Named {
|
||||||
|
return i.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) StringWithinTransport() string {
|
||||||
|
if len(i.container.Names) > 0 {
|
||||||
|
return i.container.Names[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) DeleteImage(*types.SystemContext) error {
|
||||||
|
// we were never here
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) PolicyConfigurationIdentity() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) PolicyConfigurationNamespaces() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageRef) Transport() types.ImageTransport {
|
||||||
|
return is.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageSource) Close() {
|
||||||
|
err := os.RemoveAll(i.path)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error removing %q: %v", i.path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageSource) Reference() types.ImageReference {
|
||||||
|
return i.ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageSource) GetSignatures() ([][]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
||||||
|
return []byte{}, "", fmt.Errorf("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageSource) GetManifest() ([]byte, string, error) {
|
||||||
|
return i.manifest, v1.MediaTypeImageManifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *containerImageSource) GetBlob(blob types.BlobInfo) (reader io.ReadCloser, size int64, err error) {
|
||||||
|
if blob.Digest == i.configDigest {
|
||||||
|
logrus.Debugf("start reading config")
|
||||||
|
reader := bytes.NewReader(i.config)
|
||||||
|
closer := func() error {
|
||||||
|
logrus.Debugf("finished reading config")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ioutils.NewReadCloserWrapper(reader, closer), reader.Size(), nil
|
||||||
|
}
|
||||||
|
layerFile, err := os.OpenFile(filepath.Join(i.path, blob.Digest.String()), os.O_RDONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error reading layer %q: %v", blob.Digest.String(), err)
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
size = -1
|
||||||
|
st, err := layerFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("error reading size of layer %q: %v", blob.Digest.String(), err)
|
||||||
|
} else {
|
||||||
|
size = st.Size()
|
||||||
|
}
|
||||||
|
logrus.Debugf("reading layer %q", blob.Digest.String())
|
||||||
|
return layerFile, size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeContainerImageRef(store storage.Store, container *storage.Container, config string) types.ImageReference {
|
||||||
|
var err error
|
||||||
|
var name reference.Named
|
||||||
|
if len(container.Names) > 0 {
|
||||||
|
name, err = reference.ParseNamed(container.Names[0])
|
||||||
|
if err != nil {
|
||||||
|
name = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &containerImageRef{
|
||||||
|
store: store,
|
||||||
|
container: container,
|
||||||
|
name: name,
|
||||||
|
config: []byte(config),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containers/storage/pkg/reexec"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if reexec.Init() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "stevedore"
|
||||||
|
app.Usage = "minimal image builder"
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "root",
|
||||||
|
Usage: "storage root dir",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "runroot",
|
||||||
|
Usage: "storage state dir",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "storage-driver",
|
||||||
|
Usage: "storage driver",
|
||||||
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "storage-option",
|
||||||
|
Usage: "storage driver option",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "signature-policy",
|
||||||
|
Usage: "signature policy path",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "debug",
|
||||||
|
Usage: "print debugging information",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Before = func(c *cli.Context) error {
|
||||||
|
logrus.SetLevel(logrus.ErrorLevel)
|
||||||
|
if c.GlobalIsSet("debug") {
|
||||||
|
if c.GlobalBool("debug") {
|
||||||
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
app.After = func(c *cli.Context) error {
|
||||||
|
store, err := getStore(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store.Shutdown(false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "from",
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
Usage: "create a working container based on an image",
|
||||||
|
Description: "creates a working container based on an image",
|
||||||
|
Flags: fromFlags,
|
||||||
|
Action: fromCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "mount",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "mount and create a symbolic link to a working container's filesystem root",
|
||||||
|
Description: "mounts and creates a symbolic link to a working container's filesystem root",
|
||||||
|
Flags: mountFlags,
|
||||||
|
Action: mountCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "umount",
|
||||||
|
Aliases: []string{"u", "unmount"},
|
||||||
|
Usage: "unmount and remove a symbolic link to a working container's filesystem root",
|
||||||
|
Description: "unmounts and removes a symbolic link to a working container's filesystem root",
|
||||||
|
Flags: umountFlags,
|
||||||
|
Action: umountCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "commit",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "create an image from a working container",
|
||||||
|
Description: "creates an image from a working container",
|
||||||
|
Flags: append(commitFlags, configFlags...),
|
||||||
|
Action: commitCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "delete",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "delete a working container",
|
||||||
|
Description: "deletes a working container",
|
||||||
|
Flags: deleteFlags,
|
||||||
|
Action: deleteCmd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mountFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "name of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "root",
|
||||||
|
Usage: "a previous root directory of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "link",
|
||||||
|
Usage: "name of a symlink to create",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func mountCmd(c *cli.Context) error {
|
||||||
|
store, err := getStore(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
if c.IsSet("name") {
|
||||||
|
name = c.String("name")
|
||||||
|
}
|
||||||
|
root := ""
|
||||||
|
if c.IsSet("root") {
|
||||||
|
root = c.String("root")
|
||||||
|
}
|
||||||
|
link := ""
|
||||||
|
if c.IsSet("link") {
|
||||||
|
link = c.String("link")
|
||||||
|
if link == "" {
|
||||||
|
return fmt.Errorf("link location can not be empty")
|
||||||
|
}
|
||||||
|
abs, err := filepath.Abs(link)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting link path %q to absolute path: %v", link, err)
|
||||||
|
}
|
||||||
|
link = abs
|
||||||
|
}
|
||||||
|
if name == "" && root == "" {
|
||||||
|
return fmt.Errorf("either --name or --root, or both, must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := lookupContainer(store, name, root, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mdata, err := store.GetMetadata(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
metadata := ContainerMetadata{}
|
||||||
|
err = json.Unmarshal([]byte(mdata), &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPoint, err := store.Mount(container.ID, "")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error mounting container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
present := false
|
||||||
|
for _, m := range metadata.Mounts {
|
||||||
|
if m == mountPoint {
|
||||||
|
present = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !present {
|
||||||
|
metadata.Mounts = append(append([]string{}, metadata.Mounts...), mountPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if link != "" {
|
||||||
|
err = os.Symlink(mountPoint, link)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating symlink to %q: %v", mountPoint, err)
|
||||||
|
}
|
||||||
|
metadata.Links = append(append([]string{}, metadata.Links...), link)
|
||||||
|
}
|
||||||
|
|
||||||
|
mdata2, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = store.SetMetadata(container.ID, string(mdata2))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
umountFlags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "name of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "root",
|
||||||
|
Usage: "root directory of the working container",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "link",
|
||||||
|
Usage: "symlink to the root directory of the working container",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func umountCmd(c *cli.Context) error {
|
||||||
|
store, err := getStore(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
if c.IsSet("name") {
|
||||||
|
name = c.String("name")
|
||||||
|
}
|
||||||
|
root := ""
|
||||||
|
if c.IsSet("root") {
|
||||||
|
root = c.String("root")
|
||||||
|
}
|
||||||
|
link := ""
|
||||||
|
if c.IsSet("link") {
|
||||||
|
link = c.String("link")
|
||||||
|
if link == "" {
|
||||||
|
return fmt.Errorf("link location can not be empty")
|
||||||
|
}
|
||||||
|
abs, err := filepath.Abs(link)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting link path %q to absolute path: %v", link, err)
|
||||||
|
}
|
||||||
|
link = abs
|
||||||
|
}
|
||||||
|
if name == "" && root == "" && link == "" {
|
||||||
|
return fmt.Errorf("either --name or --root or --link, or some combination, must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := lookupContainer(store, name, root, link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.Unmount(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mdata, err := store.GetMetadata(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
metadata := ContainerMetadata{}
|
||||||
|
err = json.Unmarshal([]byte(mdata), &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, link := range metadata.Links {
|
||||||
|
err = os.Remove(link)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error removing symlink %q: %v", link, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata.Links = nil
|
||||||
|
|
||||||
|
mdata2, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = store.SetMetadata(container.ID, string(mdata2))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue