1
0
Fork 0
forked from barak/tarpoon
tarpoon/builddir.go

256 lines
5.7 KiB
Go

package main
import (
"compress/gzip"
"crypto/sha256"
"encoding/json"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/containers/image/copy"
"github.com/containers/image/docker"
"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/containers/image/types"
)
type BuildDir struct {
dir string
manifest manifestSchema2
image image
}
func NewBuildDir() (*BuildDir, error) {
t, err := ioutil.TempDir("", "tarpoon")
if err != nil {
return nil, err
}
d := &BuildDir{
dir: t,
}
return d, nil
}
func (t *BuildDir) LoadBase(base string) error {
if base == "scratch" {
return t.createEmptyBase()
}
pc, err := getPolicyContext()
if err != nil {
return err
}
defer pc.Destroy()
src, err := docker.ParseReference(base)
if err != nil {
return err
}
dest, err := transports.ParseImageName("dir:" + t.dir)
if err != nil {
return err
}
// TODO: sigs
err = copy.Image(systemContext(), pc, dest, src, &copy.Options{
RemoveSignatures: false,
SignBy: "",
ReportWriter: os.Stdout,
})
if err != nil {
return err
}
manifest, err := os.Open(filepath.Join(t.dir, "manifest.json"))
if err != nil {
return err
}
defer manifest.Close()
dec := json.NewDecoder(manifest)
err = dec.Decode(&t.manifest)
if err != nil {
return err
}
configfile := t.manifest.ConfigDescriptor.Digest[7:] + ".tar"
image, err := os.Open(filepath.Join(t.dir, configfile))
if err != nil {
return err
}
defer image.Close()
dec = json.NewDecoder(image)
err = dec.Decode(&t.image)
if err != nil {
return err
}
return nil
}
func systemContext() *types.SystemContext {
tlsVerify := true
return &types.SystemContext{
RegistriesDirPath: "",
DockerCertPath: "",
DockerInsecureSkipTLSVerify: !tlsVerify
}
}
func (t *BuildDir) createEmptyBase() error {
t.manifest = manifestSchema2{
SchemaVersion: 2,
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
}
t.image = image{
v1Image: v1Image{
Created: time.Now(),
ContainerConfig: &config{
Cmd: []string{"/bin/sh", "-c"},
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
},
Architecture: "amd64",
OS: "linux",
Author: "tarpoon",
},
RootFS: &rootFS{
Type: "layers",
},
}
return nil
}
func (t *BuildDir) DumpImageConfig() error {
path := filepath.Join(t.dir, "_config.json")
configf, err := os.Create(path)
if err != nil {
return err
}
enc := json.NewEncoder(configf)
err = enc.Encode(t.image)
if err != nil {
configf.Close()
return err
}
configf.Sync()
configf.Close()
sha := sha256File(path)
t.manifest.ConfigDescriptor = descriptor{
Size: sha.Size(),
Digest: sha.String(),
MediaType: "application/vnd.docker.container.image.v1+json",
}
return os.Rename(path, filepath.Join(t.dir, sha.Name()+".tar"))
}
func (t *BuildDir) WriteManifest() error {
path := filepath.Join(t.dir, "manifest.json")
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", "\t")
return enc.Encode(t.manifest)
}
func (t *BuildDir) AddTarLayer(tarfile string) error {
at := time.Now()
sha, err := t.gzipLayer(tarfile)
if err != nil {
return err
}
gzpath := filepath.Join(t.dir, "_layer.gz")
gzsha := sha256File(gzpath)
t.image.RootFS.DiffIDs = append(t.image.RootFS.DiffIDs, sha.String())
t.image.History = append(t.image.History, imageHistory{
Created: at,
CreatedBy: "#(nop) TARPOON",
Comment: "Created by tarpoon",
})
t.image.Created = at
t.manifest.LayersDescriptors = append(t.manifest.LayersDescriptors, descriptor{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Size: gzsha.Size(),
Digest: gzsha.String(),
})
return os.Rename(gzpath, filepath.Join(t.dir, gzsha.Name()+".tar"))
}
func (t *BuildDir) gzipLayer(tarfile string) (fileSha, error) {
targetpath := filepath.Join(t.dir, "_layer.gz")
tar, err := os.Open(tarfile)
if err != nil {
return fileSha{}, err
}
defer tar.Close()
tarsha := sha256.New()
tr := io.TeeReader(tar, tarsha)
gz, err := os.Create(targetpath)
if err != nil {
return fileSha{}, err
}
defer gz.Close()
gw := gzip.NewWriter(gz)
io.Copy(gw, tr)
gw.Flush()
gw.Close()
return fileSha{tarsha.Sum(nil), 0}, nil
}
func (t *BuildDir) Push(dockerTarget string) error {
pc, err := getPolicyContext()
if err != nil {
return err
}
defer pc.Destroy()
src, err := transports.ParseImageName("dir:" + t.dir)
if err != nil {
return err
}
dest, err := docker.ParseReference(dockerTarget)
if err != nil {
return err
}
// TODO: sigs
err = copy.Image(systemContext(), pc, dest, src, &copy.Options{
RemoveSignatures: false,
SignBy: "",
ReportWriter: os.Stdout,
})
return err
}
func (t *BuildDir) Close() error {
return os.RemoveAll(t.dir)
}
func getPolicyContext() (*signature.PolicyContext, error) {
policy, err := signature.DefaultPolicy(nil)
if err == nil {
return signature.NewPolicyContext(policy)
}
// Okay, so. I know DefaultPolicy explicitly says not to do this.
// But until /etc/containers/policy.json (a RedHat-ism) becomes either
// the standard or there's a better way, let's go ahead and use the default policy
// that skopeo installs anyway
policy, err = signature.NewPolicyFromBytes([]byte(rhDefaultPolicy))
if err != nil {
return nil, err
}
return signature.NewPolicyContext(policy)
}
const rhDefaultPolicy = `
{
"default": [
{
"type": "insecureAcceptAnything"
}
],
"transports":
{
"docker-daemon":
{
"": [{"type":"insecureAcceptAnything"}]
}
}
}
`