// 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 (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"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 // 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])
|
|
|
|
if len(run.Arguments) > numInnerArgs {
|
|
command += " " + strings.Join(quoteAll(run.Arguments[numInnerArgs:]), " ")
|
|
}
|
|
|
|
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 // 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))
|
|
|
|
for i, run := range runAll.Runs {
|
|
commands[i] = run.Compile()[0]
|
|
}
|
|
|
|
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 // source file/directory paths
|
|
Destination string // destination path
|
|
}
|
|
|
|
// Compile quotes the defined source files/directories and destination.
|
|
//
|
|
func (copy Copy) Compile() []string {
|
|
dest := copy.Destination
|
|
|
|
// If there is more than 1 file being copied, the destination must be a
|
|
// directory ending with "/"
|
|
if len(copy.Sources) > 1 && !strings.HasSuffix(copy.Destination, "/") {
|
|
dest = dest + "/"
|
|
}
|
|
|
|
return append(quoteAll(copy.Sources), quote(dest))
|
|
}
|
|
|
|
// CopyAs is a concrete build instruction for copying source
|
|
// files/directories and setting their ownership to the given UID/GID.
|
|
//
|
|
// While it can technically wrap any build.Instruction, it is meant to be used
|
|
// with build.Copy and build.CopyFrom to enforce file/directory ownership.
|
|
//
|
|
type CopyAs struct {
|
|
UID uint // owner UID
|
|
GID uint // owner GID
|
|
Instruction
|
|
}
|
|
|
|
// Compile returns the variant name unquoted and all quoted CopyAs instruction
|
|
// fields.
|
|
//
|
|
func (ca CopyAs) Compile() []string {
|
|
return append([]string{fmt.Sprintf("%d:%d", ca.UID, ca.GID)}, ca.Instruction.Compile()...)
|
|
}
|
|
|
|
// CopyFrom is a concrete build instruction for copying source
|
|
// files/directories from one variant image to another.
|
|
//
|
|
type CopyFrom struct {
|
|
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()...)
|
|
}
|
|
|
|
// EntryPoint is a build instruction for declaring a container's default
|
|
// runtime process.
|
|
type EntryPoint struct {
|
|
Command []string // command and arguments
|
|
}
|
|
|
|
// Compile returns the quoted entrypoint command and arguments.
|
|
//
|
|
func (ep EntryPoint) Compile() []string {
|
|
return quoteAll(ep.Command)
|
|
}
|
|
|
|
// Env is a concrete build instruction for declaring a container's runtime
|
|
// environment variables.
|
|
//
|
|
type Env struct {
|
|
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 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 // number of meta-data 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 (label Label) Compile() []string {
|
|
return compileSortedKeyValues(label.Definitions)
|
|
}
|
|
|
|
// User is a build instruction for setting which user will run future
|
|
// commands.
|
|
//
|
|
type User struct {
|
|
Name string // user name
|
|
}
|
|
|
|
// Compile returns the quoted user name.
|
|
//
|
|
func (user User) Compile() []string {
|
|
return []string{quote(user.Name)}
|
|
}
|
|
|
|
// WorkingDirectory is a build instruction for defining the working directory
|
|
// for future command and entrypoint instructions.
|
|
//
|
|
type WorkingDirectory struct {
|
|
Path string // working directory path
|
|
}
|
|
|
|
// Compile returns the quoted working directory path.
|
|
//
|
|
func (wd WorkingDirectory) Compile() []string {
|
|
return []string{quote(wd.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)
|
|
}
|
|
|
|
func quoteAll(arguments []string) []string {
|
|
quoted := make([]string, len(arguments))
|
|
|
|
for i, arg := range arguments {
|
|
quoted[i] = quote(arg)
|
|
}
|
|
|
|
return quoted
|
|
}
|
|
|
|
func sprintf(format string, arguments []string) string {
|
|
args := make([]interface{}, len(arguments))
|
|
|
|
for i, v := range arguments {
|
|
args[i] = quote(v)
|
|
}
|
|
|
|
return fmt.Sprintf(format, args...)
|
|
}
|