summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2017-05-09 09:54:09 -0700
committerDan Duvall <dduvall@wikimedia.org>2017-05-10 20:41:09 -0700
commitfb363428ed0e4bcbe7aee83f70a34981f4f092c0 (patch)
tree869706acf2182cd41c5d7bf4b4f98b1635086362
parentd0ada7682fa323c52940c99113a8809aecfae06b (diff)
downloadblubber-fb363428ed0e4bcbe7aee83f70a34981f4f092c0.tar.gz
Decouple Docker compiler from apt/npm providers
Establish phases within Docker compiler to allow providers (apt, npm, etc.) to inject their own run/copy instructions into to the Dockerfile compilation process while leaving the compiler agnostic to the providers themselves. The instructions and phases are also generalized to leave room for alternative compilers should they be needed in the future (e.g. aci support via acbuild) but also as a general design constraint to leave compiler implementation concerns out of providers.
-rw-r--r--build/instructions.go13
-rw-r--r--build/phases.go14
-rw-r--r--config/apt.go25
-rw-r--r--config/common.go19
-rw-r--r--config/config.go4
-rw-r--r--config/npm.go39
-rw-r--r--config/runs.go40
-rw-r--r--docker/compiler.go29
8 files changed, 131 insertions, 52 deletions
diff --git a/build/instructions.go b/build/instructions.go
new file mode 100644
index 0000000..2676a75
--- /dev/null
+++ b/build/instructions.go
@@ -0,0 +1,13 @@
+package build
+
+type InstructionType int
+
+const (
+ Run InstructionType = iota
+ Copy
+)
+
+type Instruction struct {
+ Type InstructionType
+ Arguments []string
+}
diff --git a/build/phases.go b/build/phases.go
new file mode 100644
index 0000000..095263c
--- /dev/null
+++ b/build/phases.go
@@ -0,0 +1,14 @@
+package build
+
+type Phase int
+
+const (
+ PhasePrivileged Phase = iota
+ PhasePrivilegeDropped
+ PhasePreInstall
+ PhasePostInstall
+)
+
+type PhaseCompileable interface {
+ InstructionsForPhase(phase Phase) []Instruction
+}
diff --git a/config/apt.go b/config/apt.go
index f9e631f..65dad9c 100644
--- a/config/apt.go
+++ b/config/apt.go
@@ -1,8 +1,8 @@
package config
import (
- "bytes"
"strings"
+ "github.com/marxarelli/blubber/build"
)
type AptConfig struct {
@@ -13,16 +13,19 @@ func (apt *AptConfig) Merge(apt2 AptConfig) {
apt.Packages = append(apt.Packages, apt2.Packages...)
}
-func (apt AptConfig) Commands() []string {
- if len(apt.Packages) < 1 {
- return []string{}
+func (apt AptConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
+ if len(apt.Packages) > 0 {
+ switch phase {
+ case build.PhasePrivileged:
+ return []build.Instruction{
+ {build.Run, []string{
+ "apt-get update && apt-get install -y ",
+ strings.Join(apt.Packages, " "),
+ " && rm -rf /var/lib/apt/lists/*",
+ }},
+ }
+ }
}
- buffer := new(bytes.Buffer)
-
- buffer.WriteString("apt-get update && apt-get install -y ")
- buffer.WriteString(strings.Join(apt.Packages, " "))
- buffer.WriteString(" && rm -rf /var/lib/apt/lists/*")
-
- return []string{buffer.String()}
+ return []build.Instruction{}
}
diff --git a/config/common.go b/config/common.go
index c4dc2db..1073b18 100644
--- a/config/common.go
+++ b/config/common.go
@@ -1,5 +1,9 @@
package config
+import (
+ "github.com/marxarelli/blubber/build"
+)
+
type CommonConfig struct {
Base string `yaml:"base"`
Apt AptConfig `yaml:"apt"`
@@ -24,3 +28,18 @@ func (cc1 *CommonConfig) Merge(cc2 CommonConfig) {
cc1.EntryPoint = cc2.EntryPoint
}
}
+
+
+func (cc *CommonConfig) PhaseCompileableConfig() []build.PhaseCompileable {
+ return []build.PhaseCompileable{cc.Apt, cc.Npm, cc.Runs}
+}
+
+func (cc *CommonConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
+ instructions := []build.Instruction{}
+
+ for _, phaseCompileable := range cc.PhaseCompileableConfig() {
+ instructions = append(instructions, phaseCompileable.InstructionsForPhase(phase)...)
+ }
+
+ return instructions
+}
diff --git a/config/config.go b/config/config.go
index dcf1adb..ee38ea8 100644
--- a/config/config.go
+++ b/config/config.go
@@ -4,7 +4,3 @@ type Config struct {
CommonConfig `yaml:",inline"`
Variants map[string]VariantConfig `yaml:"variants"`
}
-
-type CommandCompileable interface {
- Commands() []string
-}
diff --git a/config/npm.go b/config/npm.go
index c4a52c1..78307b6 100644
--- a/config/npm.go
+++ b/config/npm.go
@@ -2,8 +2,12 @@ package config
import (
"bytes"
+ "path"
+ "github.com/marxarelli/blubber/build"
)
+const TempNpmInstallDir = "/tmp/node-deps/"
+
type NpmConfig struct {
Install bool `yaml:"install"`
Env string `yaml:"env"`
@@ -17,18 +21,29 @@ func (npm *NpmConfig) Merge(npm2 NpmConfig) {
}
}
-func (npm NpmConfig) Commands() []string {
- if !npm.Install {
- return []string{}
- }
-
- buffer := new(bytes.Buffer)
-
- buffer.WriteString("npm install")
-
- if npm.Env == "production" {
- buffer.WriteString(" --production && npm dedupe")
+func (npm NpmConfig) InstructionsForPhase(phase build.Phase) []build.Instruction{
+ if npm.Install {
+ switch phase {
+ case build.PhasePreInstall:
+ npmCmd := new(bytes.Buffer)
+
+ npmCmd.WriteString("npm install")
+
+ if npm.Env == "production" {
+ npmCmd.WriteString(" --production && npm dedupe")
+ }
+
+ return []build.Instruction{
+ {build.Run, []string{"mkdir -p ", TempNpmInstallDir}},
+ {build.Copy, []string{"package.json", TempNpmInstallDir}},
+ {build.Run, []string{"cd ", TempNpmInstallDir, " && ", npmCmd.String()}},
+ }
+ case build.PhasePostInstall:
+ return []build.Instruction{
+ {build.Run, []string{"mv ", path.Join(TempNpmInstallDir, "node_modules"), " ./"}},
+ }
+ }
}
- return []string{buffer.String()}
+ return []build.Instruction{}
}
diff --git a/config/runs.go b/config/runs.go
index 70f03a9..1d1f63a 100644
--- a/config/runs.go
+++ b/config/runs.go
@@ -2,7 +2,7 @@ package config
import (
"strconv"
- "strings"
+ "github.com/marxarelli/blubber/build"
)
type RunsConfig struct {
@@ -19,26 +19,32 @@ func (run *RunsConfig) Merge(run2 RunsConfig) {
if run2.Gid != 0 { run.Gid = run2.Gid }
}
-func (run RunsConfig) Commands() []string {
- cmds := []string{}
+func (run RunsConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
+ ins := []build.Instruction{}
- if run.In != "" {
- cmds = append(cmds, strings.Join([]string{"mkdir -p", run.In}, " "))
- }
-
- if run.As != "" {
- cmd := []string{
- "groupadd -o -g", strconv.Itoa(run.Gid), "-r", run.As, "&&",
- "useradd -o -m -r -g", run.As, "-u", strconv.Itoa(run.Uid), run.As,
+ switch phase {
+ case build.PhasePrivileged:
+ if run.In != "" {
+ ins = append(ins, []build.Instruction{{build.Run, []string{"mkdir -p ", run.In}}}...)
}
- cmds = append(cmds, strings.Join(cmd, " "))
-
- if run.In != "" {
- owner := strings.Join([]string{run.As, ":", run.As}, "")
- cmds = append(cmds, strings.Join([]string{"chown", owner, run.In}, " "))
+ if run.As != "" {
+ ins = append(ins, []build.Instruction{
+ {build.Run, []string{
+ "groupadd -o -g ", strconv.Itoa(run.Gid), " -r ", run.As, " && ",
+ "useradd -o -m -r -g ", run.As, " -u ", strconv.Itoa(run.Uid), " ", run.As,
+ }},
+ }...)
+
+ if run.In != "" {
+ ins = append(ins, []build.Instruction{
+ {build.Run, []string{
+ "chown ", run.As, ":", run.As, " ", run.In,
+ }},
+ }...)
+ }
}
}
- return cmds
+ return ins
}
diff --git a/docker/compiler.go b/docker/compiler.go
index f5114c4..6b08355 100644
--- a/docker/compiler.go
+++ b/docker/compiler.go
@@ -3,6 +3,7 @@ package docker
import (
"bytes"
"strings"
+ "github.com/marxarelli/blubber/build"
"github.com/marxarelli/blubber/config"
)
@@ -33,26 +34,27 @@ func CompileStage(buffer *bytes.Buffer, stage string, vcfg *config.VariantConfig
Writeln(buffer, "FROM ", vcfg.Base, " AS ", stage)
Writeln(buffer, "USER root")
- Writeln(buffer, "WORKDIR /srv")
- CompileToCommands(buffer, vcfg.Apt)
- CompileToCommands(buffer, vcfg.Runs)
+
+ CompilePhase(buffer, vcfg, build.PhasePrivileged)
if vcfg.Runs.As != "" {
Writeln(buffer, "USER ", vcfg.Runs.As)
}
+ CompilePhase(buffer, vcfg, build.PhasePrivilegeDropped)
+
if vcfg.Runs.In != "" {
Writeln(buffer, "WORKDIR ", vcfg.Runs.In)
}
+ CompilePhase(buffer, vcfg, build.PhasePreInstall)
+
if vcfg.SharedVolume {
Writeln(buffer, "VOLUME [\"", vcfg.Runs.In, "\"]")
} else {
Writeln(buffer, "COPY . \"", vcfg.Runs.In, "\"")
}
- CompileToCommands(buffer, vcfg.Npm)
-
// Artifact copying
for _, artifact := range vcfg.Artifacts {
Write(buffer, "COPY ")
@@ -64,14 +66,25 @@ func CompileStage(buffer *bytes.Buffer, stage string, vcfg *config.VariantConfig
Writeln(buffer, artifact.Source, " ", artifact.Destination)
}
+ CompilePhase(buffer, vcfg, build.PhasePostInstall)
+
if len(vcfg.EntryPoint) > 0 {
Writeln(buffer, "ENTRYPOINT [\"", strings.Join(vcfg.EntryPoint, "\", \""), "\"]")
}
}
-func CompileToCommands(buffer *bytes.Buffer, compileable config.CommandCompileable) {
- for _, command := range compileable.Commands() {
- Writeln(buffer, "RUN ", command)
+func CompilePhase(buffer *bytes.Buffer, vcfg *config.VariantConfig, phase build.Phase) {
+ for _, instruction := range vcfg.InstructionsForPhase(phase) {
+ CompileInstruction(buffer, instruction)
+ }
+}
+
+func CompileInstruction(buffer *bytes.Buffer, instruction build.Instruction) {
+ switch instruction.Type {
+ case build.Run:
+ Writeln(buffer, append([]string{"RUN "}, instruction.Arguments...)...)
+ case build.Copy:
+ Writeln(buffer, "COPY \"", instruction.Arguments[0], "\" \"", instruction.Arguments[1], "\"")
}
}