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