New build instruction: ONBUILD defines a trigger to execute when extending an image with a new build
| ... | ... |
@@ -108,9 +108,26 @@ func (b *buildFile) CmdFrom(name string) error {
|
| 108 | 108 |
if b.config.Env == nil || len(b.config.Env) == 0 {
|
| 109 | 109 |
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") |
| 110 | 110 |
} |
| 111 |
+ // Process ONBUILD triggers if they exist |
|
| 112 |
+ if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
|
|
| 113 |
+ fmt.Fprintf(b.errStream, "# Executing %d build triggers\n", nTriggers) |
|
| 114 |
+ } |
|
| 115 |
+ for n, step := range b.config.OnBuild {
|
|
| 116 |
+ if err := b.BuildStep(fmt.Sprintf("onbuild-%d", n), step); err != nil {
|
|
| 117 |
+ return err |
|
| 118 |
+ } |
|
| 119 |
+ } |
|
| 120 |
+ b.config.OnBuild = []string{}
|
|
| 111 | 121 |
return nil |
| 112 | 122 |
} |
| 113 | 123 |
|
| 124 |
+// The ONBUILD command declares a build instruction to be executed in any future build |
|
| 125 |
+// using the current image as a base. |
|
| 126 |
+func (b *buildFile) CmdOnbuild(trigger string) error {
|
|
| 127 |
+ b.config.OnBuild = append(b.config.OnBuild, trigger) |
|
| 128 |
+ return b.commit("", b.config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
|
|
| 129 |
+} |
|
| 130 |
+ |
|
| 114 | 131 |
func (b *buildFile) CmdMaintainer(name string) error {
|
| 115 | 132 |
b.maintainer = name |
| 116 | 133 |
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
|
| ... | ... |
@@ -680,28 +697,11 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
| 680 | 680 |
if len(line) == 0 || line[0] == '#' {
|
| 681 | 681 |
continue |
| 682 | 682 |
} |
| 683 |
- tmp := strings.SplitN(line, " ", 2) |
|
| 684 |
- if len(tmp) != 2 {
|
|
| 685 |
- return "", fmt.Errorf("Invalid Dockerfile format")
|
|
| 686 |
- } |
|
| 687 |
- instruction := strings.ToLower(strings.Trim(tmp[0], " ")) |
|
| 688 |
- arguments := strings.Trim(tmp[1], " ") |
|
| 689 |
- |
|
| 690 |
- method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
|
| 691 |
- if !exists {
|
|
| 692 |
- fmt.Fprintf(b.errStream, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction)) |
|
| 693 |
- continue |
|
| 683 |
+ if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil {
|
|
| 684 |
+ return "", err |
|
| 694 | 685 |
} |
| 695 |
- |
|
| 696 | 686 |
stepN += 1 |
| 697 |
- fmt.Fprintf(b.outStream, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments) |
|
| 698 |
- |
|
| 699 |
- ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
|
| 700 |
- if ret != nil {
|
|
| 701 |
- return "", ret.(error) |
|
| 702 |
- } |
|
| 703 | 687 |
|
| 704 |
- fmt.Fprintf(b.outStream, " ---> %s\n", utils.TruncateID(b.image)) |
|
| 705 | 688 |
} |
| 706 | 689 |
if b.image != "" {
|
| 707 | 690 |
fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image)) |
| ... | ... |
@@ -713,6 +713,31 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
| 713 | 713 |
return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n")
|
| 714 | 714 |
} |
| 715 | 715 |
|
| 716 |
+// BuildStep parses a single build step from `instruction` and executes it in the current context. |
|
| 717 |
+func (b *buildFile) BuildStep(name, expression string) error {
|
|
| 718 |
+ fmt.Fprintf(b.outStream, "Step %s : %s\n", name, expression) |
|
| 719 |
+ tmp := strings.SplitN(expression, " ", 2) |
|
| 720 |
+ if len(tmp) != 2 {
|
|
| 721 |
+ return fmt.Errorf("Invalid Dockerfile format")
|
|
| 722 |
+ } |
|
| 723 |
+ instruction := strings.ToLower(strings.Trim(tmp[0], " ")) |
|
| 724 |
+ arguments := strings.Trim(tmp[1], " ") |
|
| 725 |
+ |
|
| 726 |
+ method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
|
| 727 |
+ if !exists {
|
|
| 728 |
+ fmt.Fprintf(b.errStream, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction)) |
|
| 729 |
+ return nil |
|
| 730 |
+ } |
|
| 731 |
+ |
|
| 732 |
+ ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
|
| 733 |
+ if ret != nil {
|
|
| 734 |
+ return ret.(error) |
|
| 735 |
+ } |
|
| 736 |
+ |
|
| 737 |
+ fmt.Fprintf(b.outStream, " ---> %s\n", utils.TruncateID(b.image)) |
|
| 738 |
+ return nil |
|
| 739 |
+} |
|
| 740 |
+ |
|
| 716 | 741 |
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig, authConfigFile *auth.ConfigFile) BuildFile {
|
| 717 | 742 |
return &buildFile{
|
| 718 | 743 |
runtime: srv.runtime, |
| ... | ... |
@@ -402,6 +402,64 @@ the image. |
| 402 | 402 |
The ``WORKDIR`` instruction sets the working directory in which |
| 403 | 403 |
the command given by ``CMD`` is executed. |
| 404 | 404 |
|
| 405 |
+3.11 ONBUILD |
|
| 406 |
+------------ |
|
| 407 |
+ |
|
| 408 |
+ ``ONBUILD [INSTRUCTION]`` |
|
| 409 |
+ |
|
| 410 |
+The ``ONBUILD`` instruction adds to the image a "trigger" instruction to be |
|
| 411 |
+executed at a later time, when the image is used as the base for another build. |
|
| 412 |
+The trigger will be executed in the context of the downstream build, as if it |
|
| 413 |
+had been inserted immediately after the *FROM* instruction in the downstream |
|
| 414 |
+Dockerfile. |
|
| 415 |
+ |
|
| 416 |
+Any build instruction can be registered as a trigger. |
|
| 417 |
+ |
|
| 418 |
+This is useful if you are building an image which will be used as a base to build |
|
| 419 |
+other images, for example an application build environment or a daemon which may be |
|
| 420 |
+customized with user-specific configuration. |
|
| 421 |
+ |
|
| 422 |
+For example, if your image is a reusable python application builder, it will require |
|
| 423 |
+application source code to be added in a particular directory, and it might require |
|
| 424 |
+a build script to be called *after* that. You can't just call *ADD* and *RUN* now, |
|
| 425 |
+because you don't yet have access to the application source code, and it will be |
|
| 426 |
+different for each application build. You could simply provide application developers |
|
| 427 |
+with a boilerplate Dockerfile to copy-paste into their application, but that is |
|
| 428 |
+inefficient, error-prone and difficult to update because it mixes with |
|
| 429 |
+application-specific code. |
|
| 430 |
+ |
|
| 431 |
+The solution is to use *ONBUILD* to register in advance instructions to run later, |
|
| 432 |
+during the next build stage. |
|
| 433 |
+ |
|
| 434 |
+Here's how it works: |
|
| 435 |
+ |
|
| 436 |
+1. When it encounters an *ONBUILD* instruction, the builder adds a trigger to |
|
| 437 |
+ the metadata of the image being built. |
|
| 438 |
+ The instruction does not otherwise affect the current build. |
|
| 439 |
+ |
|
| 440 |
+2. At the end of the build, a list of all triggers is stored in the image manifest, |
|
| 441 |
+ under the key *OnBuild*. They can be inspected with *docker inspect*. |
|
| 442 |
+ |
|
| 443 |
+3. Later the image may be used as a base for a new build, using the *FROM* instruction. |
|
| 444 |
+ As part of processing the *FROM* instruction, the downstream builder looks for *ONBUILD* |
|
| 445 |
+ triggers, and executes them in the same order they were registered. If any of the |
|
| 446 |
+ triggers fail, the *FROM* instruction is aborted which in turn causes the build |
|
| 447 |
+ to fail. If all triggers succeed, the FROM instruction completes and the build |
|
| 448 |
+ continues as usual. |
|
| 449 |
+ |
|
| 450 |
+4. Triggers are cleared from the final image after being executed. In other words |
|
| 451 |
+ they are not inherited by "grand-children" builds. |
|
| 452 |
+ |
|
| 453 |
+For example you might add something like this: |
|
| 454 |
+ |
|
| 455 |
+.. code-block:: bash |
|
| 456 |
+ |
|
| 457 |
+ [...] |
|
| 458 |
+ ONBUILD ADD . /app/src |
|
| 459 |
+ ONBUILD RUN /usr/local/bin/python-build --dir /app/src |
|
| 460 |
+ [...] |
|
| 461 |
+ |
|
| 462 |
+ |
|
| 405 | 463 |
.. _dockerfile_examples: |
| 406 | 464 |
|
| 407 | 465 |
4. Dockerfile Examples |
| ... | ... |
@@ -847,3 +847,19 @@ func TestBuildFailsDockerfileEmpty(t *testing.T) {
|
| 847 | 847 |
t.Fatal("Expected: %v, got: %v", docker.ErrDockerfileEmpty, err)
|
| 848 | 848 |
} |
| 849 | 849 |
} |
| 850 |
+ |
|
| 851 |
+func TestBuildOnBuildTrigger(t *testing.T) {
|
|
| 852 |
+ _, err := buildImage(testContextTemplate{`
|
|
| 853 |
+ from {IMAGE}
|
|
| 854 |
+ onbuild run echo here is the trigger |
|
| 855 |
+ onbuild run touch foobar |
|
| 856 |
+ `, |
|
| 857 |
+ nil, nil, |
|
| 858 |
+ }, |
|
| 859 |
+ t, nil, true, |
|
| 860 |
+ ) |
|
| 861 |
+ if err != nil {
|
|
| 862 |
+ t.Fatal(err) |
|
| 863 |
+ } |
|
| 864 |
+ // FIXME: test that the 'foobar' file was created in the final build. |
|
| 865 |
+} |