You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
6.0 KiB

  1. // Package build defines types and interfaces that could potentially be
  2. // compiled to various external build-tool scripts but share a general
  3. // internal abstraction and rules for escaping.
  4. //
  5. package build
  6. import (
  7. "fmt"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. )
  12. // Instruction defines a common interface that all concrete build types must
  13. // implement.
  14. //
  15. type Instruction interface {
  16. Compile() []string
  17. }
  18. // Run is a concrete build instruction for passing any number of arguments to
  19. // a shell command.
  20. //
  21. // The command string may contain inner argument placeholders using the "%s"
  22. // format verb and will be appended with the quoted values of any arguments
  23. // that remain after interpolation of the command string.
  24. //
  25. type Run struct {
  26. Command string // command string (e.g. "useradd -d %s -u %s")
  27. Arguments []string // command arguments both inner and final (e.g. ["/home/user", "123", "user"])
  28. }
  29. // Compile quotes all arguments, interpolates the command string with inner
  30. // arguments, and appends the final arguments.
  31. //
  32. func (run Run) Compile() []string {
  33. numInnerArgs := strings.Count(run.Command, `%`) - strings.Count(run.Command, `%%`)
  34. command := sprintf(run.Command, run.Arguments[0:numInnerArgs])
  35. if len(run.Arguments) > numInnerArgs {
  36. command += " " + strings.Join(quoteAll(run.Arguments[numInnerArgs:]), " ")
  37. }
  38. return []string{command}
  39. }
  40. // RunAll is a concrete build instruction for declaring multiple Run
  41. // instructions that will be executed together in a `cmd1 && cmd2` chain.
  42. //
  43. type RunAll struct {
  44. Runs []Run // multiple Run instructions to be executed together
  45. }
  46. // Compile concatenates all individually compiled Run instructions into a
  47. // single command.
  48. //
  49. func (runAll RunAll) Compile() []string {
  50. commands := make([]string, len(runAll.Runs))
  51. for i, run := range runAll.Runs {
  52. commands[i] = run.Compile()[0]
  53. }
  54. return []string{strings.Join(commands, " && ")}
  55. }
  56. // Copy is a concrete build instruction for copying source files/directories
  57. // from the build host into the image.
  58. //
  59. type Copy struct {
  60. Sources []string // source file/directory paths
  61. Destination string // destination path
  62. }
  63. // Compile quotes the defined source files/directories and destination.
  64. //
  65. func (copy Copy) Compile() []string {
  66. dest := copy.Destination
  67. // If there is more than 1 file being copied, the destination must be a
  68. // directory ending with "/"
  69. if len(copy.Sources) > 1 && !strings.HasSuffix(copy.Destination, "/") {
  70. dest = dest + "/"
  71. }
  72. return append(quoteAll(copy.Sources), quote(dest))
  73. }
  74. // CopyAs is a concrete build instruction for copying source
  75. // files/directories and setting their ownership to the given UID/GID.
  76. //
  77. // While it can technically wrap any build.Instruction, it is meant to be used
  78. // with build.Copy and build.CopyFrom to enforce file/directory ownership.
  79. //
  80. type CopyAs struct {
  81. UID uint // owner UID
  82. GID uint // owner GID
  83. Instruction
  84. }
  85. // Compile returns the variant name unquoted and all quoted CopyAs instruction
  86. // fields.
  87. //
  88. func (ca CopyAs) Compile() []string {
  89. return append([]string{fmt.Sprintf("%d:%d", ca.UID, ca.GID)}, ca.Instruction.Compile()...)
  90. }
  91. // CopyFrom is a concrete build instruction for copying source
  92. // files/directories from one variant image to another.
  93. //
  94. type CopyFrom struct {
  95. From string // source variant name
  96. Copy
  97. }
  98. // Compile returns the variant name unquoted and all quoted Copy instruction
  99. // fields.
  100. //
  101. func (cf CopyFrom) Compile() []string {
  102. return append([]string{cf.From}, cf.Copy.Compile()...)
  103. }
  104. // EntryPoint is a build instruction for declaring a container's default
  105. // runtime process.
  106. type EntryPoint struct {
  107. Command []string // command and arguments
  108. }
  109. // Compile returns the quoted entrypoint command and arguments.
  110. //
  111. func (ep EntryPoint) Compile() []string {
  112. return quoteAll(ep.Command)
  113. }
  114. // Env is a concrete build instruction for declaring a container's runtime
  115. // environment variables.
  116. //
  117. type Env struct {
  118. Definitions map[string]string // number of key/value pairs
  119. }
  120. // Compile returns the key/value pairs as a number of `key="value"` strings
  121. // where the values are properly quoted and the slice is ordered by the keys.
  122. //
  123. func (env Env) Compile() []string {
  124. return compileSortedKeyValues(env.Definitions)
  125. }
  126. // Label is a concrete build instruction for declaring a number of meta-data
  127. // key/value pairs to be included in the image.
  128. //
  129. type Label struct {
  130. Definitions map[string]string // number of meta-data key/value pairs
  131. }
  132. // Compile returns the key/value pairs as a number of `key="value"` strings
  133. // where the values are properly quoted and the slice is ordered by the keys.
  134. //
  135. func (label Label) Compile() []string {
  136. return compileSortedKeyValues(label.Definitions)
  137. }
  138. // User is a build instruction for setting which user will run future
  139. // commands.
  140. //
  141. type User struct {
  142. Name string // user name
  143. }
  144. // Compile returns the quoted user name.
  145. //
  146. func (user User) Compile() []string {
  147. return []string{quote(user.Name)}
  148. }
  149. // WorkingDirectory is a build instruction for defining the working directory
  150. // for future command and entrypoint instructions.
  151. //
  152. type WorkingDirectory struct {
  153. Path string // working directory path
  154. }
  155. // Compile returns the quoted working directory path.
  156. //
  157. func (wd WorkingDirectory) Compile() []string {
  158. return []string{quote(wd.Path)}
  159. }
  160. func compileSortedKeyValues(keyValues map[string]string) []string {
  161. defs := make([]string, 0, len(keyValues))
  162. names := make([]string, 0, len(keyValues))
  163. for name := range keyValues {
  164. names = append(names, name)
  165. }
  166. sort.Strings(names)
  167. for _, name := range names {
  168. defs = append(defs, name+"="+quote(keyValues[name]))
  169. }
  170. return defs
  171. }
  172. func quote(arg string) string {
  173. return strconv.Quote(arg)
  174. }
  175. func quoteAll(arguments []string) []string {
  176. quoted := make([]string, len(arguments))
  177. for i, arg := range arguments {
  178. quoted[i] = quote(arg)
  179. }
  180. return quoted
  181. }
  182. func sprintf(format string, arguments []string) string {
  183. args := make([]interface{}, len(arguments))
  184. for i, v := range arguments {
  185. args[i] = quote(v)
  186. }
  187. return fmt.Sprintf(format, args...)
  188. }