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 }