summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2017-10-24 18:04:47 -0700
committerDan Duvall <dduvall@wikimedia.org>2017-11-06 13:25:20 -0800
commit515d9f26eb616a9c3a0b70ba6eca88570a15b0ef (patch)
tree0f8d3b0a6381f4ea2c43ce063f6af9990a2abb2f
parent5d4cc80edf4fdba60069a7ef1e450117195fb987 (diff)
downloadblubber-515d9f26eb616a9c3a0b70ba6eca88570a15b0ef.tar.gz
Documented all exported types, functions, and interfaces
Summary: Wrote inline documentation for all the things. Fixes T168000 Test Plan: Execute `godoc -http=:6060` and proofread all the things. Reviewers: thcipriani, hashar, #release-engineering-team, demon Reviewed By: thcipriani, #release-engineering-team, demon Tags: #release-engineering-team Maniphest Tasks: T168000 Differential Revision: https://phabricator.wikimedia.org/D841
-rw-r--r--build/instructions.go72
-rw-r--r--build/phases.go19
-rw-r--r--config/apt.go13
-rw-r--r--config/artifacts.go24
-rw-r--r--config/common.go24
-rw-r--r--config/config.go6
-rw-r--r--config/flag.go9
-rw-r--r--config/node.go31
-rw-r--r--config/reader.go6
-rw-r--r--config/runs.go37
-rw-r--r--config/variant.go24
-rw-r--r--docker/compiler.go9
-rw-r--r--docker/instructions.go34
-rw-r--r--main.go2
14 files changed, 271 insertions, 39 deletions
diff --git a/build/instructions.go b/build/instructions.go
index 41c959d..3035b9f 100644
--- a/build/instructions.go
+++ b/build/instructions.go
@@ -1,3 +1,7 @@
+// Package build defines types and interfaces that could potentially be
+// compiled to various external build-tool scripts but share a general
+// internal abstraction and rules for escaping.
+//
package build
import (
@@ -7,15 +11,28 @@ import (
"strings"
)
+// Instruction defines a common interface that all concrete build types must
+// implement.
+//
type Instruction interface {
Compile() []string
}
+// Run is a concrete build instruction for passing any number of arguments to
+// a shell command.
+//
+// The command string may contain inner argument placeholders using the "%s"
+// format verb and will be appended with the quoted values of any arguments
+// that remain after interpolation of the command string.
+//
type Run struct {
- Command string
- Arguments []string
+ Command string // command string (e.g. "useradd -d %s -u %s")
+ Arguments []string // command arguments both inner and final (e.g. ["/home/user", "123", "user"])
}
+// Compile quotes all arguments, interpolates the command string with inner
+// arguments, and appends the final arguments.
+//
func (run Run) Compile() []string {
numInnerArgs := strings.Count(run.Command, `%`) - strings.Count(run.Command, `%%`)
command := sprintf(run.Command, run.Arguments[0:numInnerArgs])
@@ -27,10 +44,16 @@ func (run Run) Compile() []string {
return []string{command}
}
+// RunAll is a concrete build instruction for declaring multiple Run
+// instructions that will be executed together in a `cmd1 && cmd2` chain.
+//
type RunAll struct {
- Runs []Run
+ Runs []Run // multiple Run instructions to be executed together
}
+// Compile concatenates all individually compiled Run instructions into a
+// single command.
+//
func (runAll RunAll) Compile() []string {
commands := make([]string, len(runAll.Runs))
@@ -41,47 +64,72 @@ func (runAll RunAll) Compile() []string {
return []string{strings.Join(commands, " && ")}
}
+// Copy is a concrete build instruction for copying source files/directories
+// from the build host into the image.
+//
type Copy struct {
- Sources []string
- Destination string
+ Sources []string // source file/directory paths
+ Destination string // destination path
}
+// Compile quotes the defined source files/directories and destination.
+//
func (copy Copy) Compile() []string {
return append(quoteAll(copy.Sources), quote(copy.Destination))
}
+// CopyFrom is a concrete build instruction for copying source
+// files/directories from one variant image to another.
+//
type CopyFrom struct {
- From string
+ From string // source variant name
Copy
}
+// Compile returns the variant name unquoted and all quoted Copy instruction
+// fields.
+//
func (cf CopyFrom) Compile() []string {
return append([]string{cf.From}, cf.Copy.Compile()...)
}
+// Env is a concrete build instruction for declaring a container's runtime
+// environment variables.
+//
type Env struct {
- Definitions map[string]string
+ Definitions map[string]string // number of key/value pairs
}
+// Compile returns the key/value pairs as a number of `key="value"` strings
+// where the values are properly quoted and the slice is ordered by the keys.
+//
func (env Env) Compile() []string {
return compileSortedKeyValues(env.Definitions)
}
-// Label represents a number of meta-data key/value pairs
+// Label is a concrete build instruction for declaring a number of meta-data
+// key/value pairs to be included in the image.
+//
type Label struct {
- Definitions map[string]string
+ Definitions map[string]string // number of meta-data key/value pairs
}
-// Compile returns the label key/value pairs as a number of `key="value"`
-// strings where the values are properly quoted
+// Compile returns the key/value pairs as a number of `key="value"` strings
+// where the values are properly quoted and the slice is ordered by the keys.
+//
func (label Label) Compile() []string {
return compileSortedKeyValues(label.Definitions)
}
+// Volume is a concrete build instruction for defining a volume mount point
+// within the container.
+//
type Volume struct {
- Path string
+ Path string // volume/mount path
}
+// Compile returns the quoted volume path.
+//
func (vol Volume) Compile() []string {
return []string{quote(vol.Path)}
}
diff --git a/build/phases.go b/build/phases.go
index 02e84e4..3480927 100644
--- a/build/phases.go
+++ b/build/phases.go
@@ -1,15 +1,24 @@
package build
+// Phase enum type
type Phase int
+// Distinct build phases that each compiler implementation should pass to
+// PhaseCompileable configuration (in the order they are defined here) to
+// allow for dependency injection during compilation.
+//
const (
- PhasePrivileged Phase = iota
- PhasePrivilegeDropped
- PhasePreInstall
- PhaseInstall
- PhasePostInstall
+ PhasePrivileged Phase = iota // first, copies/execution done as root
+ PhasePrivilegeDropped // second, copies/execution done as unprivileged user from here on
+ PhasePreInstall // third, before application files and artifacts are copied
+ PhaseInstall // fourth, application files and artifacts are copied
+ PhasePostInstall // fifth, after application files and artifacts are copied
)
+// PhaseCompileable defines and interface that all configuration types should
+// implement if they want to inject build instructions into any of the defined
+// build phases.
+//
type PhaseCompileable interface {
InstructionsForPhase(phase Phase) []Instruction
}
diff --git a/config/apt.go b/config/apt.go
index e1f8f53..3a7a688 100644
--- a/config/apt.go
+++ b/config/apt.go
@@ -4,14 +4,27 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
)
+// AptConfig represents configuration pertaining to package installation from
+// existing APT sources.
+//
type AptConfig struct {
Packages []string `yaml:"packages"`
}
+// Merge takes another AptConfig and combines the packages declared within
+// with the packages of this AptConfig.
+//
func (apt *AptConfig) Merge(apt2 AptConfig) {
apt.Packages = append(apt.Packages, apt2.Packages...)
}
+// InstructionsForPhase injects build instructions that will install the
+// declared packages during the privileged phase.
+//
+// PhasePrivileged
+//
+// Updates the APT cache, installs configured packages, and cleans up.
+//
func (apt AptConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
if len(apt.Packages) > 0 {
switch phase {
diff --git a/config/artifacts.go b/config/artifacts.go
index 654f10f..bd1bdeb 100644
--- a/config/artifacts.go
+++ b/config/artifacts.go
@@ -4,12 +4,30 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
)
+// ArtifactsConfig declares files and directories to be copied from one
+// variant's container to another during the build.
+//
+// The most common use of such "multi-stage" builds is to compile and test
+// software using one variant image that contains a comprehensive set of
+// development dependencies, and copy the software binaries or production only
+// source files over into a smaller image that contains only production
+// dependencies. For a shorthand configuration of this precise pattern, use
+// VariantConfig.Copies.
+//
type ArtifactsConfig struct {
- From string `yaml:"from"`
- Source string `yaml:"source"`
- Destination string `yaml:"destination"`
+ From string `yaml:"from"` // source variant from which to copy
+ Source string `yaml:"source"` // source path within variant from which to copy
+ Destination string `yaml:"destination"` // destination path within current variant
}
+// InstructionsForPhase injects instructions into the given build phase that
+// copy configured artifacts.
+//
+// PhasePostInstall
+//
+// Injects build.CopyFrom instructions for the configured source and
+// destination paths.
+//
func (ac ArtifactsConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
switch phase {
case build.PhasePostInstall:
diff --git a/config/common.go b/config/common.go
index dae0013..11357d8 100644
--- a/config/common.go
+++ b/config/common.go
@@ -4,15 +4,20 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
)
+// CommonConfig holds the configuration fields common to both the root config
+// and each configured variant.
+//
type CommonConfig struct {
- Base string `yaml:"base"`
- Apt AptConfig `yaml:"apt"`
- Node NodeConfig `yaml:"node"`
- Runs RunsConfig `yaml:"runs"`
- SharedVolume Flag `yaml:"sharedvolume"`
- EntryPoint []string `yaml:"entrypoint"`
+ Base string `yaml:"base"` // name/path to base image
+ Apt AptConfig `yaml:"apt"` // APT related configuration
+ Node NodeConfig `yaml:"node"` // Node related configuration
+ Runs RunsConfig `yaml:"runs"` // runtime environment configuration
+ SharedVolume Flag `yaml:"sharedvolume"` // define a volume instead of copying in source files
+ EntryPoint []string `yaml:"entrypoint"` // entry-point executable
}
+// Merge takes another CommonConfig and merges its fields this one's.
+//
func (cc1 *CommonConfig) Merge(cc2 CommonConfig) {
if cc2.Base != "" {
cc1.Base = cc2.Base
@@ -28,10 +33,17 @@ func (cc1 *CommonConfig) Merge(cc2 CommonConfig) {
}
}
+// PhaseCompileableConfig returns all fields that implement
+// build.PhaseCompileable in the order that their instructions should be
+// injected.
+//
func (cc *CommonConfig) PhaseCompileableConfig() []build.PhaseCompileable {
return []build.PhaseCompileable{cc.Apt, cc.Node, cc.Runs}
}
+// InstructionsForPhase injects instructions into the given build phase for
+// each member field that supports it.
+//
func (cc *CommonConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
instructions := []build.Instruction{}
diff --git a/config/config.go b/config/config.go
index 6f4283f..3360012 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,5 +1,11 @@
+// Package config provides the internal representation of Blubber
+// configuration parsed from YAML. Each configuration type may implement
+// its own hooks for injecting build instructions into the compiler.
+//
package config
+// Config holds the root fields of a Blubber configuration.
+//
type Config struct {
CommonConfig `yaml:",inline"`
Variants map[string]VariantConfig `yaml:"variants"`
diff --git a/config/flag.go b/config/flag.go
index b841819..6fb9e72 100644
--- a/config/flag.go
+++ b/config/flag.go
@@ -1,10 +1,16 @@
package config
+// Flag represents a nullable boolean value that is considered null until
+// either parsed from YAML or merged in from another Flag value.
+//
type Flag struct {
True bool
set bool
}
+// UnmarshalYAML implements yaml.Unmarshaler to parse the underlying boolean
+// value and detect that the Flag should no longer be considered null.
+//
func (flag *Flag) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&flag.True); err != nil {
return err
@@ -15,6 +21,9 @@ func (flag *Flag) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
+// Merge takes another flag and, if set, merged its boolean value into this
+// one.
+//
func (flag *Flag) Merge(flag2 Flag) {
if flag2.set {
flag.True = flag2.True
diff --git a/config/node.go b/config/node.go
index 90859e8..561c915 100644
--- a/config/node.go
+++ b/config/node.go
@@ -5,11 +5,17 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
)
+// NodeConfig holds configuration fields related to the Node environment and
+// whether/how to install NPM packages.
+//
type NodeConfig struct {
- Dependencies Flag `yaml:"dependencies"`
- Env string `yaml:"env"`
+ Dependencies Flag `yaml:"dependencies"` // install dependencies declared in package.json
+ Env string `yaml:"env"` // environment name ("production" install)
}
+// Merge takes another NodeConfig and merges its fields into this one's,
+// overwriting both the environment and dependencies flag.
+//
func (nc *NodeConfig) Merge(nc2 NodeConfig) {
nc.Dependencies.Merge(nc2.Dependencies)
@@ -18,6 +24,27 @@ func (nc *NodeConfig) Merge(nc2 NodeConfig) {
}
}
+// InstructionsForPhase injects instructions into the build related to Node
+// dependency installation and setting of the NODE_ENV, NODE_PATH, and PATH
+// environment variables.
+//
+// PhasePreInstall
+//
+// Installs Node package dependencies declared in package.json into the shared
+// library directory (/opt/lib). Only production related packages are install
+// if NodeConfig.Env is set to "production" in which case `npm dedupe` is also
+// run. Installing dependencies during the build.PhasePreInstall phase allows
+// a compiler implementation (e.g. Docker) to produce cache-efficient output
+// so only changes to package.json will invalidate these steps of the image
+// build.
+//
+// PhasePostInstall
+//
+// Injects build.Env instructions for NODE_ENV, NODE_PATH, and PATH, setting
+// the environment according to the configuration, ensuring that packages
+// installed during build.PhasePreInstall are found by Node, and that any
+// installed binaries are found by shells.
+//
func (nc NodeConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
switch phase {
case build.PhasePreInstall:
diff --git a/config/reader.go b/config/reader.go
index 0f4cc12..7aa060d 100644
--- a/config/reader.go
+++ b/config/reader.go
@@ -35,6 +35,7 @@ func expandIncludes(config *Config, name string, included map[string]bool) ([]st
// ExpandVariant merges a named variant with a config. It also attempts to
// recursively expand any included variants in the expanded variant.
+//
func ExpandVariant(config *Config, name string) (*VariantConfig, error) {
expanded := new(VariantConfig)
expanded.CommonConfig.Merge(config.CommonConfig)
@@ -53,6 +54,8 @@ func ExpandVariant(config *Config, name string) (*VariantConfig, error) {
return expanded, nil
}
+// ReadConfig unmarshals the given YAML bytes into a Config struct.
+//
func ReadConfig(data []byte) (*Config, error) {
var config Config
@@ -61,6 +64,9 @@ func ReadConfig(data []byte) (*Config, error) {
return &config, err
}
+// ReadConfigFile unmarshals the given YAML file contents into a Config
+// struct.
+//
func ReadConfigFile(path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
diff --git a/config/runs.go b/config/runs.go
index 6a176c3..ef9f4ea 100644
--- a/config/runs.go
+++ b/config/runs.go
@@ -6,16 +6,26 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
)
+// LocalLibPrefix declares the shared directory into which application level
+// dependencies will be installed.
+//
const LocalLibPrefix = "/opt/lib"
+// RunsConfig holds configuration fields related to the application's
+// runtime environment.
+//
type RunsConfig struct {
- In string `yaml:"in"`
- As string `yaml:"as"`
- Uid int `yaml:"uid"`
- Gid int `yaml:"gid"`
- Environment map[string]string `yaml:"environment"`
+ In string `yaml:"in"` // working directory
+ As string `yaml:"as"` // unprivileged user
+ Uid int `yaml:"uid"` // unprivileged UID
+ Gid int `yaml:"gid"` // unprivileged GID
+ Environment map[string]string `yaml:"environment"` // environment variables
}
+// Merge takes another RunsConfig and overwrites this struct's fields. All
+// fields except Environment are overwritten if set. The latter is an additive
+// merge.
+//
func (run *RunsConfig) Merge(run2 RunsConfig) {
if run2.In != "" {
run.In = run2.In
@@ -39,6 +49,9 @@ func (run *RunsConfig) Merge(run2 RunsConfig) {
}
}
+// Home returns the home directory for the configured user, or /root if no
+// user is set.
+//
func (run RunsConfig) Home() string {
if run.As == "" {
return "/root"
@@ -47,6 +60,20 @@ func (run RunsConfig) Home() string {
}
}
+// InstructionsForPhase injects build instructions related to the runtime
+// configuration.
+//
+// PhasePrivileged
+//
+// Creates LocalLibPrefix directory and unprivileged user home directory,
+// creates the unprivileged user and its group, and sets up directory
+// permissions.
+//
+// PhasePrivilegeDropped
+//
+// Injects build.Env instructions for the user home directory and all
+// names/values defined by RunsConfig.Environment.
+//
func (run RunsConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
ins := []build.Instruction{}
diff --git a/config/variant.go b/config/variant.go
index 8e53b70..60fa325 100644
--- a/config/variant.go
+++ b/config/variant.go
@@ -4,19 +4,34 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
)
+// VariantConfig holds configuration fields for each defined build variant.
+//
type VariantConfig struct {
- Includes []string `yaml:"includes"`
- Copies string `yaml:"copies"`
- Artifacts []ArtifactsConfig `yaml:"artifacts"`
+ Includes []string `yaml:"includes"` // references to one or more
+ Copies string `yaml:"copies"` // copy standard artifacts from another variant
+ Artifacts []ArtifactsConfig `yaml:"artifacts"` // non-standard artifact configuration
CommonConfig `yaml:",inline"`
}
+// Merge takes another VariantConfig and overwrites this struct's fields.
+// Artifacts are merged additively.
+//
func (vc *VariantConfig) Merge(vc2 VariantConfig) {
vc.Copies = vc2.Copies
vc.Artifacts = append(vc.Artifacts, vc2.Artifacts...)
vc.CommonConfig.Merge(vc2.CommonConfig)
}
+// InstructionsForPhase injects build instructions related to artifact
+// copying, volume definition or copying of application files, and all common
+// configuration.
+//
+// PhaseInstall
+//
+// If VariantConfig.Copies is not set, either copy in application files or
+// define a shared volume. Otherwise, delegate to
+// ArtifactsConfig.InstructionsForPhase.
+//
func (vc *VariantConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
instructions := vc.CommonConfig.InstructionsForPhase(phase)
ainstructions := []build.Instruction{}
@@ -41,6 +56,9 @@ func (vc *VariantConfig) InstructionsForPhase(phase build.Phase) []build.Instruc
return instructions
}
+// VariantDependencies returns all unique names of other variants that are
+// referenced in the VariantConfig.Artifacts configuration.
+//
func (vc *VariantConfig) VariantDependencies() []string {
// get unique set of variant dependencies based on artifacts
existing := map[string]bool{}
diff --git a/docker/compiler.go b/docker/compiler.go
index 0a2120b..2823777 100644
--- a/docker/compiler.go
+++ b/docker/compiler.go
@@ -1,3 +1,6 @@
+// Package docker implements a compiler for turning Blubber configuration into
+// a valid single- or multi-stage Dockerfile.
+//
package docker
import (
@@ -9,7 +12,11 @@ import (
"phabricator.wikimedia.org/source/blubber/meta"
)
-// Compile blubber yaml file into Dockerfile
+// Compile takes a parsed config.Config and a configured variant name and
+// returns the bytes of a resulting Dockerfile. In the case where artifacts
+// are defined or the shorthand "copies" configured is set, a multi-stage
+// Dockerfile will be returned.
+//
func Compile(cfg *config.Config, variant string) (*bytes.Buffer, error) {
buffer := new(bytes.Buffer)
diff --git a/docker/instructions.go b/docker/instructions.go
index eba6731..f362814 100644
--- a/docker/instructions.go
+++ b/docker/instructions.go
@@ -8,6 +8,11 @@ import (
"phabricator.wikimedia.org/source/blubber/build"
)
+// NewDockerInstruction takes a general internal build.Instruction and returns
+// a corresponding compilable Docker specific instruction. The given internal
+// instruction is partially compiled at this point by calling Compile() which
+// applies its own logic for escaping arguments, etc.
+//
func NewDockerInstruction(instruction build.Instruction) (DockerInstruction, error) {
switch instruction.(type) {
case build.Run, build.RunAll:
@@ -39,6 +44,8 @@ func NewDockerInstruction(instruction build.Instruction) (DockerInstruction, err
return nil, errors.New("Unable to create DockerInstruction")
}
+// DockerInstruction defines an interface for instruction compilation.
+//
type DockerInstruction interface {
Compile() string
Arguments() []string
@@ -52,24 +59,36 @@ func (di abstractDockerInstruction) Arguments() []string {
return di.arguments
}
+// DockerRun compiles into a RUN instruction.
+//
type DockerRun struct{ abstractDockerInstruction }
+// Compile compiles RUN instructions.
+//
func (dr DockerRun) Compile() string {
return fmt.Sprintf(
"RUN %s\n",
join(dr.arguments, ""))
}
+// DockerCopy compiles into a COPY instruction.
+//
type DockerCopy struct{ abstractDockerInstruction }
+// Compile compiles COPY instructions.
+//
func (dc DockerCopy) Compile() string {
return fmt.Sprintf(
"COPY [%s]\n",
join(dc.arguments, ", "))
}
+// DockerCopyFrom compiles into a COPY --from instruction.
+//
type DockerCopyFrom struct{ abstractDockerInstruction }
+// Compile compiles COPY --from instructions.
+//
func (dcf DockerCopyFrom) Compile() string {
return fmt.Sprintf(
"COPY --from=%s [%s]\n",
@@ -77,26 +96,37 @@ func (dcf DockerCopyFrom) Compile() string {
join(dcf.arguments[1:], ", "))
}
+// DockerEnv compiles into a ENV instruction.
+//
type DockerEnv struct{ abstractDockerInstruction }
+// Compile compiles ENV instructions.
+//
func (de DockerEnv) Compile() string {
return fmt.Sprintf(
"ENV %s\n",
join(de.arguments, " "))
}
-// DockerLabel represents a concrete LABEL instruction
+// DockerLabel compiles into a LABEL instruction.
+//
type DockerLabel struct{ abstractDockerInstruction }
-// Compile returns multiple key="value" arguments as a single LABEL string
+// Compile returns multiple key="value" arguments as a single LABEL
+// instruction.
+//
func (dl DockerLabel) Compile() string {
return fmt.Sprintf(
"LABEL %s\n",
join(dl.arguments, " "))
}
+// DockerVolume compiles into a VOLUME instruction.
+//
type DockerVolume struct{ abstractDockerInstruction }
+// Compile compiles VOLUME instructions.
+//
func (dv DockerVolume) Compile() string {
return fmt.Sprintf(
"VOLUME [%s]\n",
diff --git a/main.go b/main.go
index a684d11..1903943 100644
--- a/main.go
+++ b/main.go
@@ -1,3 +1,5 @@
+// Package main provides the command line interface.
+//
package main
import (