Skip to content

Instantly share code, notes, and snippets.

@cescoferraro
Created November 20, 2016 13:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cescoferraro/a40dc2924e71b28d26d03e103565c3c3 to your computer and use it in GitHub Desktop.
Save cescoferraro/a40dc2924e71b28d26d03e103565c3c3 to your computer and use it in GitHub Desktop.
package image
import (
"io"
"os"
"strings"
"archive/tar"
"bytes"
"io/ioutil"
"log"
"path/filepath"
"time"
"github.com/dnephin/dobi/tasks/context"
"github.com/dnephin/dobi/utils/dockerignore"
"github.com/dnephin/dobi/utils/fs"
docker "github.com/fsouza/go-dockerclient"
)
// RunBuild builds an image if it is out of date
func RunBuild(ctx *context.ExecuteContext, t *Task, hasModifiedDeps bool) (bool, error) {
if !hasModifiedDeps {
stale, err := buildIsStale(ctx, t)
switch {
case err != nil:
return false, err
case !stale:
t.logger().Info("is fresh")
return false, nil
}
}
t.logger().Debug("is stale")
if err := buildImage(ctx, t); err != nil {
return false, err
}
image, err := GetImage(ctx, t.config)
if err != nil {
return false, err
}
record := imageModifiedRecord{ImageID: image.ID}
if err := updateImageRecord(recordPath(ctx, t.config), record); err != nil {
t.logger().Warnf("Failed to update image record: %s", err)
}
t.logger().Info("Created")
return true, nil
}
func buildIsStale(ctx *context.ExecuteContext, t *Task) (bool, error) {
image, err := GetImage(ctx, t.config)
switch err {
case docker.ErrNoSuchImage:
t.logger().Debug("Image does not exist")
return true, nil
case nil:
default:
return true, err
}
mtime, err := fs.LastModified(t.config.Context)
if err != nil {
t.logger().Warnf("Failed to get last modified time of context.")
return true, err
}
record, err := getImageRecord(recordPath(ctx, t.config))
if err != nil {
t.logger().Warnf("Failed to get image record: %s", err)
if image.Created.Before(mtime) {
t.logger().Debug("Image older than context")
return true, nil
}
return false, nil
}
if image.ID != record.ImageID || record.Info.ModTime().Before(mtime) {
t.logger().Debug("Image record older than context")
return true, nil
}
return false, nil
}
func buildImage(ctx *context.ExecuteContext, t *Task) error {
var err error
switch t.hasSteps() {
case true:
err = t.buildImageFromTarBall(ctx)
default:
err = t.buildImageFromDockerfile(ctx)
}
if err != nil {
return err
}
image, err := GetImage(ctx, t.config)
if err != nil {
return err
}
record := imageModifiedRecord{ImageID: image.ID}
return updateImageRecord(recordPath(ctx, t.config), record)
}
func buildArgs(args map[string]string) []docker.BuildArg {
out := []docker.BuildArg{}
for key, value := range args {
out = append(out, docker.BuildArg{Name: key, Value: value})
}
return out
}
func (t *Task) buildImageFromDockerfile(ctx *context.ExecuteContext) error {
err := Stream(os.Stdout, func(out io.Writer) error {
return ctx.Client.BuildImage(docker.BuildImageOptions{
Name: GetImageName(ctx, t.config),
Dockerfile: t.config.Dockerfile,
BuildArgs: buildArgs(t.config.Args),
Pull: t.config.PullBaseImageOnBuild,
RmTmpContainer: true,
ContextDir: t.config.Context,
OutputStream: out,
RawJSONStream: true,
SuppressOutput: ctx.Quiet,
})
})
if err != nil {
return err
}
return nil
}
func (t *Task) buildImageFromTarBall(ctx *context.ExecuteContext) error {
inputbuf, err := t.writeTarball()
if err != nil {
return err
}
err = Stream(os.Stdout, func(out io.Writer) error {
return ctx.Client.BuildImage(docker.BuildImageOptions{
Name: GetImageName(ctx, t.config),
BuildArgs: buildArgs(t.config.Args),
Pull: t.config.PullBaseImageOnBuild,
RmTmpContainer: true,
InputStream: inputbuf,
OutputStream: out,
RawJSONStream: true,
SuppressOutput: ctx.Quiet,
})
})
return err
}
func (t *Task) writeTarball() (*bytes.Buffer, error) {
inputbuf := bytes.NewBuffer(nil)
tr := tar.NewWriter(inputbuf)
defer tr.Close()
paths, err := t.getContext()
if err != nil {
return inputbuf, err
}
err = t.writeDockerfiletoTarBall(tr)
if err != nil {
return inputbuf, err
}
return inputbuf, t.writeFilesToTarBall(paths, tr)
}
func (t *Task) getContext() ([]string, error) {
allContext, err := t.scanContext()
if err != nil {
return []string{}, err
}
ignored, err := t.scanIgnored()
if err != nil {
return []string{}, err
}
return dockerignore.Difference(allContext, ignored), nil
}
func (t *Task) writeDockerfiletoTarBall(tr *tar.Writer) error {
rightNow := time.Now()
header := &tar.Header{Name: "Dockerfile",
Size: t.getContentSize(),
ModTime: rightNow,
AccessTime: rightNow,
ChangeTime: rightNow,
}
err := tr.WriteHeader(header)
if err != nil {
return err
}
for _, val := range t.config.Steps {
// its not a good idea to catch this error?
tr.Write([]byte(val + "\n"))
}
return err
}
func (t *Task) writeFilesToTarBall(fullpath []string, tr *tar.Writer) error {
ctx := strings.TrimPrefix(t.config.Context, "./")
ctx = strings.TrimPrefix(ctx, ".")
for _, file := range fullpath {
byt, err := ioutil.ReadFile(file)
if err != nil {
return err
}
rightNow := time.Now()
header := &tar.Header{Name: strings.TrimPrefix(file, ctx+"/"),
Size: int64(len(byt)),
ModTime: rightNow,
AccessTime: rightNow,
ChangeTime: rightNow,
}
err = tr.WriteHeader(header)
if err != nil {
return err
}
log.Printf("shoudl write %s", strings.TrimPrefix(file, ctx+"/"))
_, err = tr.Write(byt)
if err != nil {
return err
}
}
return nil
}
func (t *Task) getContentSize() int64 {
var size int64
for _, val := range t.config.Steps {
size = size + int64(len([]byte(val+"\n")))
}
return size
}
func (t *Task) scanIgnored() ([]string, error) {
bytesrece, err := ioutil.ReadFile(".dockerignore")
if err != nil {
return []string{}, nil
}
r := bytes.NewReader(bytesrece)
allIgnored, err := dockerignore.ReadAll(r)
if err != nil {
return []string{}, err
}
var resolvedignores []string
for _, val := range allIgnored {
err := filepath.Walk(val, func(path string, f os.FileInfo, err error) error {
resolvedignores = append(resolvedignores, path)
return nil
})
if err != nil {
return []string{}, err
}
}
return resolvedignores, nil
}
func (t *Task) scanContext() ([]string, error) {
fileList := []string{}
test := []string{}
err := filepath.Walk(t.config.Context, func(path string, f os.FileInfo, err error) error {
if !f.IsDir() {
fileList = append(fileList, path)
test = append(test, path)
}
return nil
})
return test, err
}
func (t *Task) hasSteps() bool {
if len(t.config.Steps) != 0 {
return true
}
return false
}
@cescoferraro
Copy link
Author

image=dobi:
    image: dobi-tar
    tags: ["latest"]
    context: .
#    dockerfile: Dockerfile
    steps:
      - FROM golang:1.7-alpine
      - ADD . /dobi
      - RUN ls /dobi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment