Prechádzať zdrojové kódy

New build instruction: ONBUILD defines a trigger to execute when extending an image with a new build

Docker-DCO-1.1-Signed-off-by: Solomon Hykes <solomon@docker.com> (github: shykes)
Solomon Hykes 11 rokov pred
rodič
commit
9f994c9646

+ 17 - 1
buildfile.go

@@ -108,9 +108,26 @@ func (b *buildFile) CmdFrom(name string) error {
 	if b.config.Env == nil || len(b.config.Env) == 0 {
 		b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
 	}
+	// Process ONBUILD triggers if they exist
+	if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
+		fmt.Fprintf(b.errStream, "# Executing %d build triggers\n", nTriggers)
+	}
+	for _, step := range b.config.OnBuild {
+		if err := b.BuildStep(step); err != nil {
+			return err
+		}
+	}
+	b.config.OnBuild = []string{}
 	return nil
 }
 
+// The ONBUILD command declares a build instruction to be executed in any future build
+// using the current image as a base.
+func (b *buildFile) CmdOnbuild(trigger string) error {
+	b.config.OnBuild = append(b.config.OnBuild, trigger)
+	return b.commit("", b.config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
+}
+
 func (b *buildFile) CmdMaintainer(name string) error {
 	b.maintainer = name
 	return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
@@ -712,7 +729,6 @@ func (b *buildFile) BuildStep(expression string) error {
 		return nil
 	}
 
-
 	ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
 	if ret != nil {
 		return ret.(error)

+ 1 - 0
container.go

@@ -99,6 +99,7 @@ type Config struct {
 	WorkingDir      string
 	Entrypoint      []string
 	NetworkDisabled bool
+	OnBuild         []string
 }
 
 func ContainerConfigFromJob(job *engine.Job) *Config {

+ 58 - 0
docs/sources/reference/builder.rst

@@ -402,6 +402,64 @@ the image.
 The ``WORKDIR`` instruction sets the working directory in which
 the command given by ``CMD`` is executed.
 
+3.11 ONBUILD
+------------
+
+    ``ONBUILD [INSTRUCTION]``
+
+The ``ONBUILD`` instruction adds to the image a "trigger" instruction to be
+executed at a later time, when the image is used as the base for another build.
+The trigger will be executed in the context of the downstream build, as if it
+had been inserted immediately after the *FROM* instruction in the downstream
+Dockerfile.
+
+Any build instruction can be registered as a trigger.
+
+This is useful if you are building an image which will be used as a base to build
+other images, for example an application build environment or a daemon which may be
+customized with user-specific configuration.
+
+For example, if your image is a reusable python application builder, it will require
+application source code to be added in a particular directory, and it might require
+a build script to be called *after* that. You can't just call *ADD* and *RUN* now,
+because you don't yet have access to the application source code, and it will be
+different for each application build. You could simply provide application developers
+with a boilerplate Dockerfile to copy-paste into their application, but that is
+inefficient, error-prone and difficult to update because it mixes with
+application-specific code.
+
+The solution is to use *ONBUILD* to register in advance instructions to run later,
+during the next build stage.
+
+Here's how it works:
+
+1. When it encounters an *ONBUILD* instruction, the builder adds a trigger to
+   the metadata of the image being built.
+   The instruction does not otherwise affect the current build.
+
+2. At the end of the build, a list of all triggers is stored in the image manifest,
+   under the key *OnBuild*. They can be inspected with *docker inspect*.
+
+3. Later the image may be used as a base for a new build, using the *FROM* instruction.
+   As part of processing the *FROM* instruction, the downstream builder looks for *ONBUILD*
+   triggers, and executes them in the same order they were registered. If any of the
+   triggers fail, the *FROM* instruction is aborted which in turn causes the build
+   to fail. If all triggers succeed, the FROM instruction completes and the build
+   continues as usual.
+
+4. Triggers are cleared from the final image after being executed. In other words
+   they are not inherited by "grand-children" builds.
+
+For example you might add something like this:
+
+.. code-block:: bash
+
+    [...]
+    ONBUILD ADD . /app/src
+    ONBUILD RUN /usr/local/bin/python-build --dir /app/src
+    [...]
+
+
 .. _dockerfile_examples:
 
 4. Dockerfile Examples

+ 16 - 0
integration/buildfile_test.go

@@ -847,3 +847,19 @@ func TestBuildFailsDockerfileEmpty(t *testing.T) {
 		t.Fatal("Expected: %v, got: %v", docker.ErrDockerfileEmpty, err)
 	}
 }
+
+func TestBuildOnBuildTrigger(t *testing.T) {
+	_, err := buildImage(testContextTemplate{`
+	from {IMAGE}
+	onbuild run echo here is the trigger
+	onbuild run touch foobar
+	`,
+		nil, nil,
+		},
+	t, nil, true,
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// FIXME: test that the 'foobar' file was created in the final build.
+}