binctr/container/image.go

179 lines
4.6 KiB
Go
Raw Normal View History

package container
import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/storage"
"github.com/docker/distribution/registry/storage/driver/filesystem"
"github.com/docker/docker/pkg/archive"
"github.com/genuinetools/reg/repoutils"
bindata "github.com/jteeuwen/go-bindata"
)
// EmbedImage pulls a docker image locally. Creates a tarball of it's contents
// and then embeds the tarball as binary data into an output bindata.go file.
func EmbedImage(image string) error {
// Get the current working directory.
wd, err := os.Getwd()
if err != nil {
return err
}
// Create our output path
output := filepath.Join(wd, "bindata.go")
// Create the temporary directory for the image contents.
tmpd, err := ioutil.TempDir("", "container-lib")
if err != nil {
return err
}
defer os.RemoveAll(tmpd) // Cleanup on complete.
// Create our tarball path.
tarball := filepath.Join(tmpd, DefaultTarballPath)
// Create our image root and state.
root := filepath.Join(tmpd, "root")
state := filepath.Join(tmpd, "state")
// Create the rootfs
if err := createRootFS(image, root, state); err != nil {
return err
}
// Create the tar.
tar, err := archive.Tar(root, archive.Gzip)
if err != nil {
return fmt.Errorf("create tar failed: %v", err)
}
// Create the tarball writer.
writer, err := os.Create(tarball)
if err != nil {
return err
}
defer writer.Close() // Close the writer.
if _, err := io.Copy(writer, tar); err != nil {
return fmt.Errorf("copy tarball failed: %v", err)
}
// Create the bindata config.
bc := bindata.NewConfig()
bc.Input = []bindata.InputConfig{
{
Path: tarball,
Recursive: false,
},
}
bc.Output = output
bc.Package = "main"
bc.NoMetadata = true
bc.Prefix = filepath.Dir(tarball)
if err := bindata.Translate(bc); err != nil {
return fmt.Errorf("bindata failed: %v", err)
}
return nil
}
// createRootFS creates the base filesystem for a docker image.
// It will pull the base image if it does not exist locally.
// This function takes in a image name and the directory where the
// rootfs should be created.
func createRootFS(image, rootfs, state string) error {
// Create the context.
ctx := context.Background()
// Create the new local registry storage.
local, err := storage.NewRegistry(ctx, filesystem.New(filesystem.DriverParameters{
RootDirectory: state,
MaxThreads: 100,
}))
if err != nil {
return fmt.Errorf("creating new registry storage failed: %v", err)
}
// Parse the repository name.
name, err := reference.ParseNormalizedNamed(image)
if err != nil {
return fmt.Errorf("not a valid image %q: %v", image, err)
}
// Add latest to the image name if it is empty.
name = reference.TagNameOnly(name)
// Get the tag for the repo.
_, tag, err := repoutils.GetRepoAndRef(image)
if err != nil {
return err
}
// Create the local repository.
repo, err := local.Repository(ctx, name)
if err != nil {
return fmt.Errorf("creating local repository for %q failed: %v", reference.Path(name), err)
}
// Create the manifest service.
ms, err := repo.Manifests(ctx)
if err != nil {
return fmt.Errorf("creating manifest service failed: %v", err)
}
// Get the specific tag.
td, err := repo.Tags(ctx).Get(ctx, tag)
// Check if we got an unknown error, that means the tag does not exist.
if err != nil && strings.Contains(err.Error(), "unknown") {
log.Println("image not found locally, pulling the image")
// Pull the image.
if err := pull(ctx, local, name, tag); err != nil {
return fmt.Errorf("pulling failed: %v", err)
}
// Try to get the tag again.
td, err = repo.Tags(ctx).Get(ctx, tag)
}
if err != nil {
return fmt.Errorf("getting local repository tag %q failed: %v", tag, err)
}
// Get the specific manifest for the tag.
manifest, err := ms.Get(ctx, td.Digest)
if err != nil {
return fmt.Errorf("getting local manifest for digest %q failed: %v", td.Digest.String(), err)
}
blobStore := repo.Blobs(ctx)
for i, ref := range manifest.References() {
if i == 0 {
fmt.Printf("skipping config %v\n", ref.Digest.String())
continue
}
fmt.Printf("unpacking %v\n", ref.Digest.String())
layer, err := blobStore.Open(ctx, ref.Digest)
if err != nil {
return fmt.Errorf("getting blob %q failed: %v", ref.Digest.String(), err)
}
// Unpack the tarfile to the mount path.
// FROM: https://godoc.org/github.com/moby/moby/pkg/archive#TarOptions
if err := archive.Untar(layer, rootfs, &archive.TarOptions{
NoLchown: true,
}); err != nil {
return fmt.Errorf("error extracting tar for %q: %v", ref.Digest.String(), err)
}
}
return nil
}