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, ©.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, ©.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"}] } } } `