Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix transient mounts in Docker 1.12 #14

Merged
merged 2 commits into from
Oct 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ To download and install the library and the binary, set up a Golang build enviro
$ go get -u github.com/openshift/imagebuilder/cmd/imagebuilder
```

The included command line takes two arguments, a path to a directory containing a dockerfile, and the name to tag the output image with.
The included command line takes one argument, a path to a directory containing a Dockerfile. The `-t` option
can be used to specify an image to tag as.

```
$ imagebuilder path_to_directory output_image_name
$ imagebuilder [-t TAG] DIRECTORY
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this a breaking change? What's our compatibility statement with respect to use of imagebuilder in scripts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and none (yet).

```

To mount a file into the image for build that will not be present in the final output image, run:
Expand All @@ -54,7 +55,18 @@ $ imagebuilder --mount ~/secrets/private.key:/etc/keys/private.key path/to/my/co

Any processes in the Dockerfile will have access to `/etc/keys/private.key`, but that file will not be part of the committed image.

Note that running `--mount` requires Docker 1.10 or newer, as it uses a Docker volume to hold the mounted files and the volume API was not available in earlier versions.
Running `--mount` requires Docker 1.10 or newer, as it uses a Docker volume to hold the mounted files and the volume API was not
available in earlier versions. In addition, there is currently a bug that blocks Docker 1.12 from using `--mount`.

You can also customize which Dockerfile is run, or run multiple Dockerfiles in sequenc (the FROM is ignored on
later files):

```
$ imagebuilder -f Dockerfile:Dockerfile.extra .
```

will build the current directory and combine the first Dockerfile with the second. The FROM in the second image
is ignored.


## Code Example
Expand Down
22 changes: 21 additions & 1 deletion builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package imagebuilder
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
Expand Down Expand Up @@ -86,7 +87,7 @@ type Builder struct {
PendingRuns []Run
PendingCopies []Copy

Executor Executor
//Executor Executor
}

func NewBuilder() *Builder {
Expand All @@ -100,6 +101,25 @@ func NewBuilder() *Builder {
}
}

func NewBuilderForReader(r io.Reader, args map[string]string) (*Builder, *parser.Node, error) {
b := NewBuilder()
b.Args = args
node, err := ParseDockerfile(r)
if err != nil {
return nil, nil, err
}
return b, node, err
}

func NewBuilderForFile(path string, args map[string]string) (*Builder, *parser.Node, error) {
f, err := os.Open(path)
if err != nil {
return nil, nil, err
}
defer f.Close()
return NewBuilderForReader(f, args)
}

// Step creates a new step from the current state.
func (b *Builder) Step() *Step {
dst := make([]string, len(b.Env)+len(b.RunConfig.Env))
Expand Down
59 changes: 45 additions & 14 deletions cmd/imagebuilder/imagebuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,34 @@ import (
docker "github.com/fsouza/go-dockerclient"
"github.com/golang/glog"

"github.com/openshift/imagebuilder"
"github.com/openshift/imagebuilder/dockerclient"
)

func main() {
log.SetFlags(0)
options := &dockerclient.ClientExecutor{}
options := dockerclient.NewClientExecutor(nil)
var tag string
var dockerfilePath string
var mountSpecs stringSliceFlag

flag.StringVar(&dockerfilePath, "dockerfile", dockerfilePath, "An optional path to a Dockerfile to use.")
flag.StringVar(&tag, "t", tag, "The name to assign this image, if any.")
flag.StringVar(&tag, "tag", tag, "The name to assign this image, if any.")
flag.StringVar(&dockerfilePath, "f", dockerfilePath, "An optional path to a Dockerfile to use. You may pass multiple docker files using the operating system delimiter.")
flag.StringVar(&dockerfilePath, "file", dockerfilePath, "An optional path to a Dockerfile to use. You may pass multiple docker files using the operating system delimiter.")
flag.Var(&mountSpecs, "mount", "An optional list of files and directories to mount during the build. Use SRC:DST syntax for each path.")
flag.BoolVar(&options.AllowPull, "allow-pull", true, "Pull the images that are not present.")
flag.BoolVar(&options.IgnoreUnrecognizedInstructions, "ignore-unrecognized-instructions", true, "If an unrecognized Docker instruction is encountered, warn but do not fail the build.")

flag.Parse()

args := flag.Args()
if len(args) != 2 {
log.Fatalf("You must provide two arguments: DIRECTORY and IMAGE_NAME")
if len(args) != 1 {
log.Fatalf("You must provide one argument, the name of a directory to build")
}

options.Directory = args[0]
options.Tag = args[1]
options.Tag = tag
if len(dockerfilePath) == 0 {
dockerfilePath = filepath.Join(options.Directory, "Dockerfile")
}
Expand Down Expand Up @@ -64,30 +69,56 @@ func main() {
// Accept ARGS on the command line
arguments := make(map[string]string)

if err := build(dockerfilePath, options, arguments); err != nil {
dockerfiles := filepath.SplitList(dockerfilePath)
if len(dockerfiles) == 0 {
dockerfiles = []string{filepath.Join(options.Directory, "Dockerfile")}
}

if err := build(dockerfiles[0], dockerfiles[1:], arguments, options); err != nil {
log.Fatal(err.Error())
}
}

func build(dockerfilePath string, e *dockerclient.ClientExecutor, arguments map[string]string) error {
func build(dockerfile string, additionalDockerfiles []string, arguments map[string]string, e *dockerclient.ClientExecutor) error {
if err := e.DefaultExcludes(); err != nil {
return fmt.Errorf("error: Could not parse default .dockerignore: %v", err)
}

client, err := docker.NewClientFromEnv()
if err != nil {
log.Fatalf("No connection to Docker available: %v", err)
return fmt.Errorf("error: No connection to Docker available: %v", err)
}
e.Client = client

f, err := os.Open(dockerfilePath)
// TODO: handle signals
defer func() {
for _, err := range e.Release() {
fmt.Fprintf(e.ErrOut, "error: Unable to clean up build: %v\n", err)
}
}()

b, node, err := imagebuilder.NewBuilderForFile(dockerfile, arguments)
if err != nil {
return err
}
defer f.Close()
if err := e.Prepare(b, node); err != nil {
return err
}
if err := e.Execute(b, node); err != nil {
return err
}

// TODO: handle signals
if err := e.Cleanup(); err != nil {
fmt.Fprintf(e.ErrOut, "error: Unable to clean up build: %v\n", err)
for _, s := range additionalDockerfiles {
_, node, err := imagebuilder.NewBuilderForFile(s, arguments)
if err != nil {
return err
}
if err := e.Execute(b, node); err != nil {
return err
}
}

return e.Build(f, arguments)
return e.Commit(b)
}

type stringSliceFlag []string
Expand Down
Loading