summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2017-10-16 11:58:00 -0700
committerDan Duvall <dduvall@wikimedia.org>2017-10-19 09:39:49 -0700
commit818a0fcc0ffcda1c80a33c4f1ff6e1cc4d52abe3 (patch)
tree9ee6e4067414506e6bd422888ea0d08dc7eefef7
parentd53a06a138106c1652fff0b8576ae907a29a727c (diff)
downloadblubber-818a0fcc0ffcda1c80a33c4f1ff6e1cc4d52abe3.tar.gz
Include meta data as labels in Dockerfile output
Summary: Certain meta data including the Blubber version and variant used at invocation may be useful in downstream tracking. Implemented abstract and Docker-specific instructions for labels and modified the Docker compiler to include these instructions at the end of the output for the final stage. Depends on D816 Fixes T178022 Test Plan: Run unit tests. Run `make && bin/blubber blubber.example.yaml production` and verify that both `blubber.version` and `blubber.variant` labels are present and accurate. Reviewers: thcipriani, hashar, #release-engineering-team Reviewed By: thcipriani, #release-engineering-team Tags: #release-engineering-team Maniphest Tasks: T178022 Differential Revision: https://phabricator.wikimedia.org/D818
-rw-r--r--build/instructions.go39
-rw-r--r--build/instructions_test.go14
-rw-r--r--docker/compiler.go49
-rw-r--r--docker/compiler_test.go19
-rw-r--r--docker/instructions.go14
-rw-r--r--docker/instructions_test.go12
6 files changed, 116 insertions, 31 deletions
diff --git a/build/instructions.go b/build/instructions.go
index 38ea322..41c959d 100644
--- a/build/instructions.go
+++ b/build/instructions.go
@@ -64,20 +64,18 @@ type Env struct {
}
func (env Env) Compile() []string {
- defs := make([]string, 0, len(env.Definitions))
- names := make([]string, 0, len(env.Definitions))
-
- for name := range env.Definitions {
- names = append(names, name)
- }
-
- sort.Strings(names)
+ return compileSortedKeyValues(env.Definitions)
+}
- for _, name := range names {
- defs = append(defs, name+"="+quote(env.Definitions[name]))
- }
+// Label represents a number of meta-data key/value pairs
+type Label struct {
+ Definitions map[string]string
+}
- return defs
+// Compile returns the label key/value pairs as a number of `key="value"`
+// strings where the values are properly quoted
+func (label Label) Compile() []string {
+ return compileSortedKeyValues(label.Definitions)
}
type Volume struct {
@@ -88,6 +86,23 @@ func (vol Volume) Compile() []string {
return []string{quote(vol.Path)}
}
+func compileSortedKeyValues(keyValues map[string]string) []string {
+ defs := make([]string, 0, len(keyValues))
+ names := make([]string, 0, len(keyValues))
+
+ for name := range keyValues {
+ names = append(names, name)
+ }
+
+ sort.Strings(names)
+
+ for _, name := range names {
+ defs = append(defs, name+"="+quote(keyValues[name]))
+ }
+
+ return defs
+}
+
func quote(arg string) string {
return strconv.Quote(arg)
}
diff --git a/build/instructions_test.go b/build/instructions_test.go
index acbdb7c..4098711 100644
--- a/build/instructions_test.go
+++ b/build/instructions_test.go
@@ -56,6 +56,20 @@ func TestEnv(t *testing.T) {
}, i.Compile())
}
+func TestLabel(t *testing.T) {
+ i := build.Label{map[string]string{
+ "fooname": "foovalue",
+ "barname": "barvalue",
+ "quxname": "quxvalue",
+ }}
+
+ assert.Equal(t, []string{
+ `barname="barvalue"`,
+ `fooname="foovalue"`,
+ `quxname="quxvalue"`,
+ }, i.Compile())
+}
+
func TestVolume(t *testing.T) {
i := build.Volume{"/foo/dir"}
diff --git a/docker/compiler.go b/docker/compiler.go
index 79f179e..0a2120b 100644
--- a/docker/compiler.go
+++ b/docker/compiler.go
@@ -6,6 +6,7 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
"phabricator.wikimedia.org/source/blubber/config"
+ "phabricator.wikimedia.org/source/blubber/meta"
)
// Compile blubber yaml file into Dockerfile
@@ -28,63 +29,73 @@ func Compile(cfg *config.Config, variant string) (*bytes.Buffer, error) {
if err != nil {
return nil, err
}
- CompileStage(buffer, stage, dependency)
+ compileStage(buffer, stage, dependency)
mainStage = variant
}
- CompileStage(buffer, mainStage, vcfg)
+ compileStage(buffer, mainStage, vcfg)
+
+ // add meta-data labels to the final stage
+ compileInstructions(buffer, build.Label{map[string]string{
+ "blubber.variant": variant,
+ "blubber.version": meta.FullVersion(),
+ }})
return buffer, nil
}
-func CompileStage(buffer *bytes.Buffer, stage string, vcfg *config.VariantConfig) {
+func compileStage(buffer *bytes.Buffer, stage string, vcfg *config.VariantConfig) {
baseAndStage := vcfg.Base
if stage != "" {
baseAndStage += " AS " + stage
}
- Writeln(buffer, "FROM ", baseAndStage)
+ writeln(buffer, "FROM ", baseAndStage)
- Writeln(buffer, "USER root")
+ writeln(buffer, "USER root")
- CompilePhase(buffer, vcfg, build.PhasePrivileged)
+ compilePhase(buffer, vcfg, build.PhasePrivileged)
if vcfg.Runs.As != "" {
- Writeln(buffer, "USER ", vcfg.Runs.As)
+ writeln(buffer, "USER ", vcfg.Runs.As)
}
- CompilePhase(buffer, vcfg, build.PhasePrivilegeDropped)
+ compilePhase(buffer, vcfg, build.PhasePrivilegeDropped)
if vcfg.Runs.In != "" {
- Writeln(buffer, "WORKDIR ", vcfg.Runs.In)
+ writeln(buffer, "WORKDIR ", vcfg.Runs.In)
}
- CompilePhase(buffer, vcfg, build.PhasePreInstall)
+ compilePhase(buffer, vcfg, build.PhasePreInstall)
- CompilePhase(buffer, vcfg, build.PhaseInstall)
+ compilePhase(buffer, vcfg, build.PhaseInstall)
- CompilePhase(buffer, vcfg, build.PhasePostInstall)
+ compilePhase(buffer, vcfg, build.PhasePostInstall)
if len(vcfg.EntryPoint) > 0 {
- Writeln(buffer, "ENTRYPOINT [\"", strings.Join(vcfg.EntryPoint, "\", \""), "\"]")
+ writeln(buffer, "ENTRYPOINT [\"", strings.Join(vcfg.EntryPoint, "\", \""), "\"]")
}
}
-func CompilePhase(buffer *bytes.Buffer, vcfg *config.VariantConfig, phase build.Phase) {
- for _, instruction := range vcfg.InstructionsForPhase(phase) {
+func compileInstructions(buffer *bytes.Buffer, instructions ...build.Instruction) {
+ for _, instruction := range instructions {
dockerInstruction, _ := NewDockerInstruction(instruction)
- Write(buffer, dockerInstruction.Compile())
+ write(buffer, dockerInstruction.Compile())
}
}
-func Write(buffer *bytes.Buffer, strings ...string) {
+func compilePhase(buffer *bytes.Buffer, vcfg *config.VariantConfig, phase build.Phase) {
+ compileInstructions(buffer, vcfg.InstructionsForPhase(phase)...)
+}
+
+func write(buffer *bytes.Buffer, strings ...string) {
for _, str := range strings {
buffer.WriteString(str)
}
}
-func Writeln(buffer *bytes.Buffer, strings ...string) {
- Write(buffer, strings...)
+func writeln(buffer *bytes.Buffer, strings ...string) {
+ write(buffer, strings...)
buffer.WriteString("\n")
}
diff --git a/docker/compiler_test.go b/docker/compiler_test.go
index 04454b4..846304b 100644
--- a/docker/compiler_test.go
+++ b/docker/compiler_test.go
@@ -8,6 +8,7 @@ import (
"phabricator.wikimedia.org/source/blubber/config"
"phabricator.wikimedia.org/source/blubber/docker"
+ "phabricator.wikimedia.org/source/blubber/meta"
)
func TestSingleStageHasNoName(t *testing.T) {
@@ -72,3 +73,21 @@ func TestMultipleArtifactsFromSameStage(t *testing.T) {
assert.Equal(t, 1, strings.Count(dockerfile, "FROM foo/bar AS build\n"))
assert.Equal(t, 1, strings.Count(dockerfile, "FROM foo/bar AS production\n"))
}
+
+func TestMetaDataLabels(t *testing.T) {
+ cfg, err := config.ReadConfig([]byte(`---
+ base: foo/bar
+ variants:
+ development: {}`))
+
+ assert.Nil(t, err)
+
+ dockerOut, _ := docker.Compile(cfg, "development")
+ dockerfile := dockerOut.String()
+
+ version := meta.FullVersion()
+
+ assert.Contains(t, dockerfile,
+ "LABEL blubber.variant=\"development\" blubber.version=\""+version+"\"\n",
+ )
+}
diff --git a/docker/instructions.go b/docker/instructions.go
index fa566cb..eba6731 100644
--- a/docker/instructions.go
+++ b/docker/instructions.go
@@ -26,6 +26,10 @@ func NewDockerInstruction(instruction build.Instruction) (DockerInstruction, err
var dockerInstruction DockerEnv
dockerInstruction.arguments = instruction.Compile()
return dockerInstruction, nil
+ case build.Label:
+ var dockerInstruction DockerLabel
+ dockerInstruction.arguments = instruction.Compile()
+ return dockerInstruction, nil
case build.Volume:
var dockerInstruction DockerVolume
dockerInstruction.arguments = instruction.Compile()
@@ -81,6 +85,16 @@ func (de DockerEnv) Compile() string {
join(de.arguments, " "))
}
+// DockerLabel represents a concrete LABEL instruction
+type DockerLabel struct{ abstractDockerInstruction }
+
+// Compile returns multiple key="value" arguments as a single LABEL string
+func (dl DockerLabel) Compile() string {
+ return fmt.Sprintf(
+ "LABEL %s\n",
+ join(dl.arguments, " "))
+}
+
type DockerVolume struct{ abstractDockerInstruction }
func (dv DockerVolume) Compile() string {
diff --git a/docker/instructions_test.go b/docker/instructions_test.go
index 87dfada..a4f648b 100644
--- a/docker/instructions_test.go
+++ b/docker/instructions_test.go
@@ -71,6 +71,18 @@ func TestEnv(t *testing.T) {
assert.Equal(t, "ENV bar=\"foo\" foo=\"bar\"\n", di.Compile())
}
+func TestLabel(t *testing.T) {
+ i := build.Label{map[string]string{"foo": "bar", "bar": "foo"}}
+
+ di, err := docker.NewDockerInstruction(i)
+
+ var dockerLabel docker.DockerLabel
+
+ assert.Nil(t, err)
+ assert.IsType(t, dockerLabel, di)
+ assert.Equal(t, "LABEL bar=\"foo\" foo=\"bar\"\n", di.Compile())
+}
+
func TestVolume(t *testing.T) {
i := build.Volume{"/foo/dir"}