First working cut
This commit is contained in:
parent
608290c74e
commit
db918f12ad
4 changed files with 436 additions and 0 deletions
256
builddir.go
Normal file
256
builddir.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
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"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
Loading…
Add table
Add a link
Reference in a new issue