func contextFromDir(contextDir string) (io.ReadCloser, error) {
	relDockerfile := "Dockerfile"
	relDockerfile = archive.CanonicalTarNameForPath(relDockerfile)
	f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	defer f.Close()
	var excludes []string
	if err == nil {
		excludes, err = dockerignore.ReadAll(f)
		if err != nil {
			return nil, err
		}
	}
	if err := validateContextDirectory(contextDir, excludes); err != nil {
		return nil, fmt.Errorf("Error checking context: '%s'.", err)
	}
	var includes = []string{"."}
	keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
	keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
	if keepThem1 || keepThem2 {
		includes = append(includes, ".dockerignore", relDockerfile)
	}
	compression := archive.Uncompressed  
	buildCtx, err := archive.TarWithOptions(contextDir, &archive.TarOptions{
		Compression:     compression,
		ExcludePatterns: excludes,
		IncludeFiles:    includes,
	})
	if err != nil {
		return nil, err
	}
	return buildCtx, nil
}
contextFromDir taken verbatim from docker/build to match logic.
package source
import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"time"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/builder/dockerignore"
	docker "github.com/docker/docker/client"
	"github.com/docker/docker/pkg/archive"
	"github.com/docker/docker/pkg/fileutils"
	"github.com/docker/docker/pkg/jsonmessage"
	git "github.com/sensiblecodeio/git-prep-directory"
)
type ImageSource interface {
	Obtain(client *docker.Client, payload []byte) (string, error)
}
type CwdSource struct{}
func (CwdSource) Name() (string, error) {
	name, err := os.Getwd()
	if err != nil {
		return "", err
	}
	return filepath.Base(name), nil
}
func (s *CwdSource) Obtain(c *docker.Client, payload []byte) (string, error) {
	imageName, err := s.Name()
	if err != nil {
		return "", err
	}
	buildPath := "."
	err = DockerBuildDirectory(c, imageName, buildPath)
	if err != nil {
		return "", err
	}
	if exists(filepath.Join(buildPath, "runtime", "Dockerfile")) {
		log.Printf("Generate runtime image")
		imageName, err = constructRuntime(c, imageName)
		if err != nil {
			return "", err
		}
	}
	return imageName, nil
}
type DockerPullSource struct {
	Repository, Tag string
}
var imageTag = regexp.MustCompile("^([^:]+):?(.*)$")
func DockerPullSourceFromImage(image string) *DockerPullSource {
	parts := imageTag.FindStringSubmatch(image)
	if len(parts) != 2 {
		log.Panicf("imageTag regexp failed to match %q", image)
	}
	image, tag := parts[0], parts[1]
	return &DockerPullSource{image, tag}
}
func (s *DockerPullSource) Obtain(c *docker.Client, payload []byte) (string, error) {
	imageName := fmt.Sprintf("%s:%s", s.Repository, s.Tag)
	rc, err := c.ImagePull(context.TODO(), imageName, types.ImagePullOptions{})
	if err != nil {
		return "", err
	}
	defer rc.Close()
	err = jsonmessage.DisplayJSONMessagesStream(rc, os.Stderr, 0, false, nil)
	if err != nil {
		return "", err
	}
	return imageName, nil
}
type GitHostSource struct {
	Host          string
	User          string
	Repository    string
	InitialBranch string
	ImageRoot string
}
func (s *GitHostSource) CloneURL() string {
	format := "https://%s/%s/%s"
	if HaveSSHKey() {
		format = "ssh://git@%s/%s/%s"
	}
	return fmt.Sprintf(format, s.Host, s.User, s.Repository)
}
func (s *GitHostSource) Ref(payload []byte) (string, error) {
	if len(payload) == 0 {
		return s.InitialBranch, nil
	}
	var v struct {
		SHA string
	}
	err := json.Unmarshal(payload, &v)
	if err != nil {
		return "", err
	}
	return v.SHA, nil
}
func (s *GitHostSource) Obtain(c *docker.Client, payload []byte) (string, error) {
	ref, err := s.Ref(payload)
	if err != nil {
		return "", err
	}
	gitDir, err := filepath.Abs(filepath.Join(".", "src", s.Host, s.User, s.Repository))
	if err != nil {
		return "", err
	}
	build, err := git.PrepBuildDirectory(gitDir, s.CloneURL(), ref, 10*time.Minute, os.Stderr)
	if err != nil {
		return "", err
	}
	defer build.Cleanup()
	dockerImage := fmt.Sprintf("%s:%s", s.Repository, build.Name)
	buildPath := filepath.Join(build.Dir, s.ImageRoot)
	err = DockerBuildDirectory(c, dockerImage, buildPath)
	if err != nil {
		return "", err
	}
	if exists(filepath.Join(buildPath, "runtime", "Dockerfile")) {
		dockerImage, err = constructRuntime(c, dockerImage)
		if err != nil {
			return "", err
		}
	}
	return dockerImage, nil
}
func constructRuntime(c *docker.Client, dockerImage string) (string, error) {
	stdout, err := DockerRun(c, dockerImage)
	if err != nil {
		return "", fmt.Errorf("run buildtime image: %v", err)
	}
	imageName := dockerImage + "-runtime"
	resp, err := c.ImageBuild(context.TODO(), stdout, types.ImageBuildOptions{
		Remove: true,
		Tags:   []string{imageName},
	})
	if err != nil {
		return "", err
	}
	_, err = io.Copy(os.Stderr, resp.Body)
	if err != nil {
		return "", err
	}
	return imageName, nil
}
func DockerRun(c *docker.Client, imageName string) (io.ReadCloser, error) {
	resp, err := c.ContainerCreate(
		context.TODO(),
		&container.Config{
			Hostname:     "generateruntimecontext",
			AttachStdout: true,
			AttachStderr: true,
			Image:        imageName,
			Labels: map[string]string{
				"orchestrator": "hanoverd",
				"purpose":      "Generate build context for runtime container",
			},
		},
		&container.HostConfig{},
		&network.NetworkingConfig{},
		nil,
		"",
	)
	if err != nil {
		log.Printf("Create container... failed: %v", err)
		return nil, err
	}
	containerID := resp.ID
	if len(resp.Warnings) > 0 {
		for _, w := range resp.Warnings {
			log.Printf("ContainerCreate warning: %v", w)
		}
	}
	r, w := io.Pipe()
	attached := make(chan struct{})
	detached := make(chan struct{})
	go func() {
		defer close(detached)
		cr, err2 := c.ContainerAttach(
			context.TODO(),
			containerID,
			types.ContainerAttachOptions{
				Logs:   true,
				Stdout: true,
				Stderr: true,
				Stream: true,
			},
		)
		if err2 != nil {
			_ = w.CloseWithError(err2)
		}
		_, err2 = io.Copy(w, cr.Reader)
		_ = w.CloseWithError(err2)
	}()
	select {
	case <-detached:
		log.Printf("Attachment failed")
		return nil, fmt.Errorf("Attachment failed")
	case <-attached:
		attached <- struct{}{}
	}
	err = c.ContainerStart(context.TODO(), containerID, types.ContainerStartOptions{})
	if err != nil {
		log.Printf("Start container... failed: %v", err)
		return nil, err
	}
	removeContainer := func() {
		err2 := c.ContainerRemove(context.TODO(), containerID, types.ContainerRemoveOptions{
			RemoveVolumes: true,
			Force:         true,
		})
		if err2 != nil {
			log.Printf("Error removing intermediate container: %v", err2)
		}
	}
	return struct {
		io.Reader
		io.Closer
	}{
		Reader: r,
		Closer: CloseFunc(func() error {
			defer removeContainer()
			waitBodyC, err2C := c.ContainerWait(context.TODO(), containerID, container.WaitConditionNotRunning)
			select {
			case err2 := <-err2C:
				return err2
			case waitBody := <-waitBodyC:
				if waitBody.Error != nil && waitBody.Error.Message != "" {
					return fmt.Errorf("containerWait: %v", waitBody.Error.Message)
				}
				if waitBody.StatusCode != 0 {
					return fmt.Errorf("non-zero exit status: %v", waitBody.StatusCode)
				}
				return nil
			}
		}),
	}, err
}
type CloseFunc func() error
func (fn CloseFunc) Close() error { return fn() }
func exists(filename string) bool {
	_, err := os.Stat(filename)
	switch {
	case err == nil:
		return true
	default:
		log.Printf("Error checking for the existence of %q: %v", filename, err)
	case os.IsNotExist(err):
	}
	return false
}
func HaveSSHKey() bool {
	keys := []string{"id_dsa", "id_ecdsa", "id_rsa", "id_ed25519"}
	for _, filename := range keys {
		path := os.ExpandEnv(filepath.Join("$HOME/.ssh", filename))
		if _, err := os.Stat(path); err == nil {
			return true
		}
	}
	return false
}
func DockerBuildDirectory(c *docker.Client, name, path string) error {
	buildCtx, err := contextFromDir(path)
	if err != nil {
		return err
	}
	resp, err := c.ImageBuild(
		context.TODO(),
		buildCtx,
		types.ImageBuildOptions{
			Remove: true,
			Tags:   []string{name},
		},
	)
	if err != nil {
		return err
	}
	err = jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stderr, 0, false, nil)
	return err
}