| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,32 @@ |
| 0 |
+package builder |
|
| 1 |
+ |
|
| 2 |
+import "github.com/openshift/origin/pkg/build/api" |
|
| 3 |
+ |
|
| 4 |
+// A KeyValue can be used to build ordered lists of key-value pairs. |
|
| 5 |
+type KeyValue struct {
|
|
| 6 |
+ Key string |
|
| 7 |
+ Value string |
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+// buildInfo returns a slice of KeyValue pairs with build metadata to be |
|
| 11 |
+// inserted into Docker images produced by build. |
|
| 12 |
+func buildInfo(build *api.Build) []KeyValue {
|
|
| 13 |
+ kv := []KeyValue{
|
|
| 14 |
+ {"OPENSHIFT_BUILD_NAME", build.Name},
|
|
| 15 |
+ {"OPENSHIFT_BUILD_NAMESPACE", build.Namespace},
|
|
| 16 |
+ {"OPENSHIFT_BUILD_SOURCE", build.Spec.Source.Git.URI},
|
|
| 17 |
+ } |
|
| 18 |
+ if build.Spec.Source.Git.Ref != "" {
|
|
| 19 |
+ kv = append(kv, KeyValue{"OPENSHIFT_BUILD_REFERENCE", build.Spec.Source.Git.Ref})
|
|
| 20 |
+ } |
|
| 21 |
+ if build.Spec.Revision != nil && build.Spec.Revision.Git != nil && build.Spec.Revision.Git.Commit != "" {
|
|
| 22 |
+ kv = append(kv, KeyValue{"OPENSHIFT_BUILD_COMMIT", build.Spec.Revision.Git.Commit})
|
|
| 23 |
+ } |
|
| 24 |
+ if build.Spec.Strategy.Type == api.SourceBuildStrategyType {
|
|
| 25 |
+ env := build.Spec.Strategy.SourceStrategy.Env |
|
| 26 |
+ for _, e := range env {
|
|
| 27 |
+ kv = append(kv, KeyValue{e.Name, e.Value})
|
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+ return kv |
|
| 31 |
+} |
| 0 | 32 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,52 @@ |
| 0 |
+package builder |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ kapi "k8s.io/kubernetes/pkg/api" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/openshift/origin/pkg/build/api" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func TestBuildInfo(t *testing.T) {
|
|
| 12 |
+ b := &api.Build{
|
|
| 13 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 14 |
+ Name: "sample-app", |
|
| 15 |
+ Namespace: "default", |
|
| 16 |
+ }, |
|
| 17 |
+ Spec: api.BuildSpec{
|
|
| 18 |
+ Source: api.BuildSource{
|
|
| 19 |
+ Git: &api.GitBuildSource{
|
|
| 20 |
+ URI: "github.com/openshift/sample-app", |
|
| 21 |
+ Ref: "master", |
|
| 22 |
+ }, |
|
| 23 |
+ }, |
|
| 24 |
+ Strategy: api.BuildStrategy{
|
|
| 25 |
+ Type: api.SourceBuildStrategyType, |
|
| 26 |
+ SourceStrategy: &api.SourceBuildStrategy{
|
|
| 27 |
+ Env: []kapi.EnvVar{
|
|
| 28 |
+ {Name: "RAILS_ENV", Value: "production"},
|
|
| 29 |
+ }, |
|
| 30 |
+ }, |
|
| 31 |
+ }, |
|
| 32 |
+ Revision: &api.SourceRevision{
|
|
| 33 |
+ Git: &api.GitSourceRevision{
|
|
| 34 |
+ Commit: "1575a90c569a7cc0eea84fbd3304d9df37c9f5ee", |
|
| 35 |
+ }, |
|
| 36 |
+ }, |
|
| 37 |
+ }, |
|
| 38 |
+ } |
|
| 39 |
+ got := buildInfo(b) |
|
| 40 |
+ want := []KeyValue{
|
|
| 41 |
+ {"OPENSHIFT_BUILD_NAME", "sample-app"},
|
|
| 42 |
+ {"OPENSHIFT_BUILD_NAMESPACE", "default"},
|
|
| 43 |
+ {"OPENSHIFT_BUILD_SOURCE", "github.com/openshift/sample-app"},
|
|
| 44 |
+ {"OPENSHIFT_BUILD_REFERENCE", "master"},
|
|
| 45 |
+ {"OPENSHIFT_BUILD_COMMIT", "1575a90c569a7cc0eea84fbd3304d9df37c9f5ee"},
|
|
| 46 |
+ {"RAILS_ENV", "production"},
|
|
| 47 |
+ } |
|
| 48 |
+ if !reflect.DeepEqual(got, want) {
|
|
| 49 |
+ t.Errorf("buildInfo(%+v) = %+v; want %+v", b, got, want)
|
|
| 50 |
+ } |
|
| 51 |
+} |
| ... | ... |
@@ -1,10 +1,7 @@ |
| 1 | 1 |
package builder |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bytes" |
|
| 5 |
- "errors" |
|
| 6 | 4 |
"fmt" |
| 7 |
- "io" |
|
| 8 | 5 |
"io/ioutil" |
| 9 | 6 |
"net" |
| 10 | 7 |
"net/url" |
| ... | ... |
@@ -19,13 +16,14 @@ import ( |
| 19 | 19 |
"github.com/golang/glog" |
| 20 | 20 |
kapi "k8s.io/kubernetes/pkg/api" |
| 21 | 21 |
|
| 22 |
- "github.com/openshift/origin/pkg/build/api" |
|
| 23 |
- "github.com/openshift/origin/pkg/build/builder/cmd/dockercfg" |
|
| 24 |
- "github.com/openshift/origin/pkg/util/docker/dockerfile" |
|
| 25 | 22 |
s2iapi "github.com/openshift/source-to-image/pkg/api" |
| 26 | 23 |
"github.com/openshift/source-to-image/pkg/scm/git" |
| 27 | 24 |
"github.com/openshift/source-to-image/pkg/tar" |
| 28 | 25 |
"github.com/openshift/source-to-image/pkg/util" |
| 26 |
+ |
|
| 27 |
+ "github.com/openshift/origin/pkg/build/api" |
|
| 28 |
+ "github.com/openshift/origin/pkg/build/builder/cmd/dockercfg" |
|
| 29 |
+ "github.com/openshift/origin/pkg/util/docker/dockerfile" |
|
| 29 | 30 |
) |
| 30 | 31 |
|
| 31 | 32 |
const ( |
| ... | ... |
@@ -48,17 +46,6 @@ type DockerBuilder struct {
|
| 48 | 48 |
urlTimeout time.Duration |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
-// MetaInstuction represent an Docker instruction used for adding metadata |
|
| 52 |
-// to Dockerfile |
|
| 53 |
-type MetaInstruction string |
|
| 54 |
- |
|
| 55 |
-const ( |
|
| 56 |
- // Label represents the LABEL Docker instruction |
|
| 57 |
- Label MetaInstruction = "LABEL" |
|
| 58 |
- // Env represents the ENV Docker instruction |
|
| 59 |
- Env MetaInstruction = "ENV" |
|
| 60 |
-) |
|
| 61 |
- |
|
| 62 | 51 |
// NewDockerBuilder creates a new instance of DockerBuilder |
| 63 | 52 |
func NewDockerBuilder(dockerClient DockerClient, build *api.Build) *DockerBuilder {
|
| 64 | 53 |
return &DockerBuilder{
|
| ... | ... |
@@ -229,44 +216,38 @@ func (d *DockerBuilder) addBuildParameters(dir string) error {
|
| 229 | 229 |
dockerfilePath = filepath.Join(dir, d.build.Spec.Source.ContextDir, "Dockerfile") |
| 230 | 230 |
} |
| 231 | 231 |
|
| 232 |
- fileStat, err := os.Lstat(dockerfilePath) |
|
| 232 |
+ f, err := os.Open(dockerfilePath) |
|
| 233 | 233 |
if err != nil {
|
| 234 | 234 |
return err |
| 235 | 235 |
} |
| 236 | 236 |
|
| 237 |
- filePerm := fileStat.Mode() |
|
| 238 |
- |
|
| 239 |
- fileData, err := ioutil.ReadFile(dockerfilePath) |
|
| 237 |
+ // Parse the Dockerfile. |
|
| 238 |
+ node, err := parser.Parse(f) |
|
| 240 | 239 |
if err != nil {
|
| 241 | 240 |
return err |
| 242 | 241 |
} |
| 243 | 242 |
|
| 244 |
- var newFileData string |
|
| 243 |
+ // Update base image if build strategy specifies the From field. |
|
| 245 | 244 |
if d.build.Spec.Strategy.DockerStrategy.From != nil && d.build.Spec.Strategy.DockerStrategy.From.Kind == "DockerImage" {
|
| 246 |
- newFileData, err = replaceValidCmd(dockercmd.From, d.build.Spec.Strategy.DockerStrategy.From.Name, fileData) |
|
| 245 |
+ err := replaceLastFrom(node, d.build.Spec.Strategy.DockerStrategy.From.Name) |
|
| 247 | 246 |
if err != nil {
|
| 248 | 247 |
return err |
| 249 | 248 |
} |
| 250 |
- } else {
|
|
| 251 |
- newFileData = newFileData + string(fileData) |
|
| 252 | 249 |
} |
| 253 | 250 |
|
| 254 |
- envVars := getBuildEnvVars(d.build) |
|
| 255 |
- newFileData = appendMetadata(Env, newFileData, envVars) |
|
| 256 |
- |
|
| 257 |
- labels := map[string]string{}
|
|
| 258 |
- sourceInfo := d.git.GetInfo(dir) |
|
| 259 |
- if len(d.build.Spec.Source.ContextDir) > 0 {
|
|
| 260 |
- sourceInfo.ContextDir = d.build.Spec.Source.ContextDir |
|
| 251 |
+ // Append build info as environment variables. |
|
| 252 |
+ err = appendEnv(node, d.buildInfo()) |
|
| 253 |
+ if err != nil {
|
|
| 254 |
+ return err |
|
| 261 | 255 |
} |
| 262 |
- labels = util.GenerateLabelsFromSourceInfo(labels, sourceInfo, api.DefaultDockerLabelNamespace) |
|
| 263 |
- newFileData = appendMetadata(Label, newFileData, labels) |
|
| 264 | 256 |
|
| 265 |
- node, err := parser.Parse(strings.NewReader(newFileData)) |
|
| 257 |
+ // Append build labels. |
|
| 258 |
+ err = appendLabel(node, d.buildLabels(dir)) |
|
| 266 | 259 |
if err != nil {
|
| 267 | 260 |
return err |
| 268 | 261 |
} |
| 269 | 262 |
|
| 263 |
+ // Insert environment variables defined in the build strategy. |
|
| 270 | 264 |
err = insertEnvAfterFrom(node, d.build.Spec.Strategy.DockerStrategy.Env) |
| 271 | 265 |
if err != nil {
|
| 272 | 266 |
return err |
| ... | ... |
@@ -274,156 +255,123 @@ func (d *DockerBuilder) addBuildParameters(dir string) error {
|
| 274 | 274 |
|
| 275 | 275 |
instructions := dockerfile.ParseTreeToDockerfile(node) |
| 276 | 276 |
|
| 277 |
- return ioutil.WriteFile(dockerfilePath, instructions, filePerm) |
|
| 277 |
+ // Overwrite the Dockerfile. |
|
| 278 |
+ fi, err := f.Stat() |
|
| 279 |
+ if err != nil {
|
|
| 280 |
+ return err |
|
| 281 |
+ } |
|
| 282 |
+ return ioutil.WriteFile(dockerfilePath, instructions, fi.Mode()) |
|
| 278 | 283 |
} |
| 279 | 284 |
|
| 280 |
-// appendMetadata appends a Docker instruction that adds metadata values |
|
| 281 |
-// to a string containing a valid Dockerfile. |
|
| 282 |
-func appendMetadata(inst MetaInstruction, fileData string, envVars map[string]string) string {
|
|
| 283 |
- if !strings.HasSuffix(fileData, "\n") {
|
|
| 284 |
- fileData += "\n" |
|
| 285 |
- } |
|
| 286 |
- first := true |
|
| 287 |
- for k, v := range envVars {
|
|
| 288 |
- if first {
|
|
| 289 |
- fileData += fmt.Sprintf("%s %s=%+q", inst, k, v)
|
|
| 290 |
- first = false |
|
| 291 |
- } else {
|
|
| 292 |
- fileData += fmt.Sprintf(" \\\n\t%s=%+q", k, v)
|
|
| 293 |
- } |
|
| 285 |
+// buildInfo converts the buildInfo output to a format that appendEnv can |
|
| 286 |
+// consume. |
|
| 287 |
+func (d *DockerBuilder) buildInfo() []dockerfile.KeyValue {
|
|
| 288 |
+ bi := buildInfo(d.build) |
|
| 289 |
+ kv := make([]dockerfile.KeyValue, len(bi)) |
|
| 290 |
+ for i, item := range bi {
|
|
| 291 |
+ kv[i] = dockerfile.KeyValue{Key: item.Key, Value: item.Value}
|
|
| 294 | 292 |
} |
| 295 |
- fileData += "\n" |
|
| 296 |
- return fileData |
|
| 293 |
+ return kv |
|
| 297 | 294 |
} |
| 298 | 295 |
|
| 299 |
-// invalidCmdErr represents an error returned from replaceValidCmd |
|
| 300 |
-// when an invalid Dockerfile command has been passed to |
|
| 301 |
-// replaceValidCmd |
|
| 302 |
-var invalidCmdErr = errors.New("invalid Dockerfile command")
|
|
| 303 |
- |
|
| 304 |
-// replaceCmdErr represents an error returned from replaceValidCmd |
|
| 305 |
-// when a command which has more than one valid occurrences inside |
|
| 306 |
-// a Dockerfile has been passed or the specified command cannot |
|
| 307 |
-// be found |
|
| 308 |
-var replaceCmdErr = errors.New("cannot replace given Dockerfile command")
|
|
| 309 |
- |
|
| 310 |
-// replaceValidCmd replaces the valid occurrence of a command |
|
| 311 |
-// in a Dockerfile with the given replaceArgs |
|
| 312 |
-func replaceValidCmd(cmd, replaceArgs string, fileData []byte) (string, error) {
|
|
| 313 |
- if _, ok := dockercmd.Commands[cmd]; !ok {
|
|
| 314 |
- return "", invalidCmdErr |
|
| 315 |
- } |
|
| 316 |
- buf := bytes.NewBuffer(fileData) |
|
| 317 |
- // Parse with Docker parser |
|
| 318 |
- node, err := parser.Parse(buf) |
|
| 319 |
- if err != nil {
|
|
| 320 |
- return "", errors.New("cannot parse Dockerfile: " + err.Error())
|
|
| 296 |
+// buildLabels returns a slice of KeyValue pairs in a format that appendEnv can |
|
| 297 |
+// consume. |
|
| 298 |
+func (d *DockerBuilder) buildLabels(dir string) []dockerfile.KeyValue {
|
|
| 299 |
+ labels := map[string]string{}
|
|
| 300 |
+ sourceInfo := d.git.GetInfo(dir) |
|
| 301 |
+ if len(d.build.Spec.Source.ContextDir) > 0 {
|
|
| 302 |
+ sourceInfo.ContextDir = d.build.Spec.Source.ContextDir |
|
| 321 | 303 |
} |
| 304 |
+ labels = util.GenerateLabelsFromSourceInfo(labels, sourceInfo, api.DefaultDockerLabelNamespace) |
|
| 305 |
+ kv := make([]dockerfile.KeyValue, len(labels)) |
|
| 306 |
+ i := 0 |
|
| 307 |
+ for k, v := range labels {
|
|
| 308 |
+ kv[i] = dockerfile.KeyValue{Key: k, Value: v}
|
|
| 309 |
+ i++ |
|
| 310 |
+ } |
|
| 311 |
+ return kv |
|
| 312 |
+} |
|
| 322 | 313 |
|
| 323 |
- pos := traverseAST(cmd, node) |
|
| 324 |
- if pos == 0 {
|
|
| 325 |
- return "", replaceCmdErr |
|
| 314 |
+// setupPullSecret provides a Docker authentication configuration when the |
|
| 315 |
+// PullSecret is specified. |
|
| 316 |
+func (d *DockerBuilder) setupPullSecret() (*docker.AuthConfigurations, error) {
|
|
| 317 |
+ if len(os.Getenv(dockercfg.PullAuthType)) == 0 {
|
|
| 318 |
+ return nil, nil |
|
| 326 | 319 |
} |
| 320 |
+ r, err := os.Open(os.Getenv(dockercfg.PullAuthType)) |
|
| 321 |
+ if err != nil {
|
|
| 322 |
+ return nil, fmt.Errorf("'%s': %s", os.Getenv(dockercfg.PullAuthType), err)
|
|
| 323 |
+ } |
|
| 324 |
+ return docker.NewAuthConfigurations(r) |
|
| 325 |
+} |
|
| 327 | 326 |
|
| 328 |
- // Re-initialize the buffer |
|
| 329 |
- buf = bytes.NewBuffer(fileData) |
|
| 330 |
- var newFileData string |
|
| 331 |
- var index int |
|
| 332 |
- var replaceNextLn bool |
|
| 333 |
- for {
|
|
| 334 |
- line, err := buf.ReadString('\n')
|
|
| 335 |
- if err != nil && err != io.EOF {
|
|
| 336 |
- return "", err |
|
| 327 |
+// dockerBuild performs a docker build on the source that has been retrieved |
|
| 328 |
+func (d *DockerBuilder) dockerBuild(dir string) error {
|
|
| 329 |
+ var noCache bool |
|
| 330 |
+ var forcePull bool |
|
| 331 |
+ if d.build.Spec.Strategy.DockerStrategy != nil {
|
|
| 332 |
+ if d.build.Spec.Source.ContextDir != "" {
|
|
| 333 |
+ dir = filepath.Join(dir, d.build.Spec.Source.ContextDir) |
|
| 337 | 334 |
} |
| 338 |
- line = strings.TrimSpace(line) |
|
| 339 |
- |
|
| 340 |
- // The current line starts with the specified command (cmd) |
|
| 341 |
- if strings.HasPrefix(strings.ToUpper(line), strings.ToUpper(cmd)) {
|
|
| 342 |
- index++ |
|
| 343 |
- |
|
| 344 |
- // The current line finishes on a backslash. |
|
| 345 |
- // All we need to do is replace the next line |
|
| 346 |
- // with our specified replaceArgs |
|
| 347 |
- if line[len(line)-1:] == "\\" && index == pos {
|
|
| 348 |
- replaceNextLn = true |
|
| 349 |
- |
|
| 350 |
- args := strings.Split(line, " ") |
|
| 351 |
- if len(args) > 2 {
|
|
| 352 |
- // Keep just our Dockerfile command and the backslash |
|
| 353 |
- newFileData += args[0] + " \\" + "\n" |
|
| 354 |
- } else {
|
|
| 355 |
- newFileData += line + "\n" |
|
| 356 |
- } |
|
| 357 |
- continue |
|
| 358 |
- } |
|
| 335 |
+ noCache = d.build.Spec.Strategy.DockerStrategy.NoCache |
|
| 336 |
+ forcePull = d.build.Spec.Strategy.DockerStrategy.ForcePull |
|
| 337 |
+ } |
|
| 338 |
+ auth, err := d.setupPullSecret() |
|
| 339 |
+ if err != nil {
|
|
| 340 |
+ return err |
|
| 341 |
+ } |
|
| 342 |
+ return buildImage(d.dockerClient, dir, noCache, d.build.Spec.Output.To.Name, d.tar, auth, forcePull) |
|
| 343 |
+} |
|
| 359 | 344 |
|
| 360 |
- // Normal ending line |
|
| 361 |
- if index == pos {
|
|
| 362 |
- line = fmt.Sprintf("%s %s", strings.ToUpper(cmd), replaceArgs)
|
|
| 345 |
+// replaceLastFrom changes the last FROM instruction of node to point to the |
|
| 346 |
+// base image image. |
|
| 347 |
+func replaceLastFrom(node *parser.Node, image string) error {
|
|
| 348 |
+ if node == nil {
|
|
| 349 |
+ return nil |
|
| 350 |
+ } |
|
| 351 |
+ for i := len(node.Children) - 1; i >= 0; i-- {
|
|
| 352 |
+ child := node.Children[i] |
|
| 353 |
+ if child != nil && child.Value == dockercmd.From {
|
|
| 354 |
+ from, err := dockerfile.From(image) |
|
| 355 |
+ if err != nil {
|
|
| 356 |
+ return err |
|
| 363 | 357 |
} |
| 364 |
- } |
|
| 365 |
- |
|
| 366 |
- // Previous line ended on a backslash |
|
| 367 |
- // This line contains command arguments |
|
| 368 |
- if replaceNextLn {
|
|
| 369 |
- if line[len(line)-1:] == "\\" {
|
|
| 370 |
- // Ignore all successive lines terminating on a backslash |
|
| 371 |
- // since they all are going to be replaced by replaceArgs |
|
| 372 |
- continue |
|
| 358 |
+ fromTree, err := parser.Parse(strings.NewReader(from)) |
|
| 359 |
+ if err != nil {
|
|
| 360 |
+ return err |
|
| 373 | 361 |
} |
| 374 |
- replaceNextLn = false |
|
| 375 |
- line = replaceArgs |
|
| 362 |
+ node.Children[i] = fromTree.Children[0] |
|
| 363 |
+ return nil |
|
| 376 | 364 |
} |
| 377 |
- |
|
| 378 |
- if err == io.EOF {
|
|
| 379 |
- // Otherwise, the new Dockerfile will have one newline |
|
| 380 |
- // more in the end |
|
| 381 |
- newFileData += line |
|
| 382 |
- break |
|
| 383 |
- } |
|
| 384 |
- newFileData += line + "\n" |
|
| 385 | 365 |
} |
| 366 |
+ return nil |
|
| 367 |
+} |
|
| 386 | 368 |
|
| 387 |
- // Parse output for validation |
|
| 388 |
- buf = bytes.NewBuffer([]byte(newFileData)) |
|
| 389 |
- if _, err := parser.Parse(buf); err != nil {
|
|
| 390 |
- return "", errors.New("cannot parse new Dockerfile: " + err.Error())
|
|
| 391 |
- } |
|
| 369 |
+// appendEnv appends an ENV Dockerfile instruction as the last child of node |
|
| 370 |
+// with keys and values from m. |
|
| 371 |
+func appendEnv(node *parser.Node, m []dockerfile.KeyValue) error {
|
|
| 372 |
+ return appendKeyValueInstruction(dockerfile.Env, node, m) |
|
| 373 |
+} |
|
| 392 | 374 |
|
| 393 |
- return newFileData, nil |
|
| 375 |
+// appendLabel appends a LABEL Dockerfile instruction as the last child of node |
|
| 376 |
+// with keys and values from m. |
|
| 377 |
+func appendLabel(node *parser.Node, m []dockerfile.KeyValue) error {
|
|
| 378 |
+ return appendKeyValueInstruction(dockerfile.Label, node, m) |
|
| 394 | 379 |
} |
| 395 | 380 |
|
| 396 |
-// traverseAST traverses the Abstract Syntax Tree output |
|
| 397 |
-// from the Docker parser and returns the valid position |
|
| 398 |
-// of the command it was requested to look for. |
|
| 399 |
-// |
|
| 400 |
-// Note that this function is intended to be used with |
|
| 401 |
-// Dockerfile commands that should be specified only once |
|
| 402 |
-// in a Dockerfile (FROM, CMD, ENTRYPOINT) |
|
| 403 |
-func traverseAST(cmd string, node *parser.Node) int {
|
|
| 404 |
- switch cmd {
|
|
| 405 |
- case dockercmd.From, dockercmd.Entrypoint, dockercmd.Cmd: |
|
| 406 |
- default: |
|
| 407 |
- return 0 |
|
| 408 |
- } |
|
| 409 |
- |
|
| 410 |
- index := 0 |
|
| 411 |
- if node.Value == cmd {
|
|
| 412 |
- index++ |
|
| 413 |
- } |
|
| 414 |
- for _, n := range node.Children {
|
|
| 415 |
- index += traverseAST(cmd, n) |
|
| 416 |
- } |
|
| 417 |
- if node.Next != nil {
|
|
| 418 |
- for n := node.Next; n != nil; n = n.Next {
|
|
| 419 |
- if len(n.Children) > 0 {
|
|
| 420 |
- index += traverseAST(cmd, n) |
|
| 421 |
- } else if n.Value == cmd {
|
|
| 422 |
- index++ |
|
| 423 |
- } |
|
| 424 |
- } |
|
| 381 |
+// appendKeyValueInstruction is a primitive used to avoid code duplication. |
|
| 382 |
+// Callers should use a derivative of this such as appendEnv or appendLabel. |
|
| 383 |
+// appendKeyValueInstruction appends a Dockerfile instruction with key-value |
|
| 384 |
+// syntax created by f as the last child of node with keys and values from m. |
|
| 385 |
+func appendKeyValueInstruction(f func([]dockerfile.KeyValue) (string, error), node *parser.Node, m []dockerfile.KeyValue) error {
|
|
| 386 |
+ if node == nil {
|
|
| 387 |
+ return nil |
|
| 425 | 388 |
} |
| 426 |
- return index |
|
| 389 |
+ instruction, err := f(m) |
|
| 390 |
+ if err != nil {
|
|
| 391 |
+ return err |
|
| 392 |
+ } |
|
| 393 |
+ return dockerfile.InsertInstructions(node, len(node.Children), instruction) |
|
| 427 | 394 |
} |
| 428 | 395 |
|
| 429 | 396 |
// insertEnvAfterFrom inserts an ENV instruction with the environment variables |
| ... | ... |
@@ -456,34 +404,3 @@ func insertEnvAfterFrom(node *parser.Node, env []kapi.EnvVar) error {
|
| 456 | 456 |
|
| 457 | 457 |
return nil |
| 458 | 458 |
} |
| 459 |
- |
|
| 460 |
-// setupPullSecret provides a Docker authentication configuration when the |
|
| 461 |
-// PullSecret is specified. |
|
| 462 |
-func (d *DockerBuilder) setupPullSecret() (*docker.AuthConfigurations, error) {
|
|
| 463 |
- if len(os.Getenv(dockercfg.PullAuthType)) == 0 {
|
|
| 464 |
- return nil, nil |
|
| 465 |
- } |
|
| 466 |
- r, err := os.Open(os.Getenv(dockercfg.PullAuthType)) |
|
| 467 |
- if err != nil {
|
|
| 468 |
- return nil, fmt.Errorf("'%s': %s", os.Getenv(dockercfg.PullAuthType), err)
|
|
| 469 |
- } |
|
| 470 |
- return docker.NewAuthConfigurations(r) |
|
| 471 |
-} |
|
| 472 |
- |
|
| 473 |
-// dockerBuild performs a docker build on the source that has been retrieved |
|
| 474 |
-func (d *DockerBuilder) dockerBuild(dir string) error {
|
|
| 475 |
- var noCache bool |
|
| 476 |
- var forcePull bool |
|
| 477 |
- if d.build.Spec.Strategy.DockerStrategy != nil {
|
|
| 478 |
- if d.build.Spec.Source.ContextDir != "" {
|
|
| 479 |
- dir = filepath.Join(dir, d.build.Spec.Source.ContextDir) |
|
| 480 |
- } |
|
| 481 |
- noCache = d.build.Spec.Strategy.DockerStrategy.NoCache |
|
| 482 |
- forcePull = d.build.Spec.Strategy.DockerStrategy.ForcePull |
|
| 483 |
- } |
|
| 484 |
- auth, err := d.setupPullSecret() |
|
| 485 |
- if err != nil {
|
|
| 486 |
- return err |
|
| 487 |
- } |
|
| 488 |
- return buildImage(d.dockerClient, dir, noCache, d.build.Spec.Output.To.Name, d.tar, auth, forcePull) |
|
| 489 |
-} |
| ... | ... |
@@ -1,259 +1,16 @@ |
| 1 | 1 |
package builder |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bytes" |
|
| 5 |
- "log" |
|
| 6 | 4 |
"reflect" |
| 7 | 5 |
"strings" |
| 8 | 6 |
"testing" |
| 9 | 7 |
|
| 10 |
- dockercmd "github.com/docker/docker/builder/command" |
|
| 11 | 8 |
"github.com/docker/docker/builder/parser" |
| 12 | 9 |
kapi "k8s.io/kubernetes/pkg/api" |
| 13 | 10 |
|
| 14 | 11 |
"github.com/openshift/origin/pkg/util/docker/dockerfile" |
| 15 | 12 |
) |
| 16 | 13 |
|
| 17 |
-func TestReplaceValidCmd(t *testing.T) {
|
|
| 18 |
- tests := []struct {
|
|
| 19 |
- name string |
|
| 20 |
- cmd string |
|
| 21 |
- replaceArgs string |
|
| 22 |
- fileData []byte |
|
| 23 |
- expectedOutput string |
|
| 24 |
- expectedDiffs int |
|
| 25 |
- expectedErr error |
|
| 26 |
- }{
|
|
| 27 |
- {
|
|
| 28 |
- name: "from-replacement", |
|
| 29 |
- cmd: dockercmd.From, |
|
| 30 |
- replaceArgs: "other/image", |
|
| 31 |
- fileData: []byte(dockerFile), |
|
| 32 |
- expectedOutput: expectedFROM, |
|
| 33 |
- expectedDiffs: 1, |
|
| 34 |
- expectedErr: nil, |
|
| 35 |
- }, |
|
| 36 |
- {
|
|
| 37 |
- name: "run-replacement", |
|
| 38 |
- cmd: dockercmd.Run, |
|
| 39 |
- replaceArgs: "This test kind-of-fails before string replacement so this string won't be used", |
|
| 40 |
- fileData: []byte(dockerFile), |
|
| 41 |
- expectedOutput: "", |
|
| 42 |
- expectedErr: replaceCmdErr, |
|
| 43 |
- }, |
|
| 44 |
- {
|
|
| 45 |
- name: "invalid-dockerfile-cmd", |
|
| 46 |
- cmd: "blabla", |
|
| 47 |
- replaceArgs: "This test fails at start so this string won't be used", |
|
| 48 |
- fileData: []byte(dockerFile), |
|
| 49 |
- expectedOutput: "", |
|
| 50 |
- expectedErr: invalidCmdErr, |
|
| 51 |
- }, |
|
| 52 |
- {
|
|
| 53 |
- name: "no-cmd-in-dockerfile", |
|
| 54 |
- cmd: dockercmd.Cmd, |
|
| 55 |
- replaceArgs: "runme.sh", |
|
| 56 |
- fileData: []byte(dockerFile), |
|
| 57 |
- expectedOutput: "", |
|
| 58 |
- expectedErr: replaceCmdErr, |
|
| 59 |
- }, |
|
| 60 |
- {
|
|
| 61 |
- name: "trailing-slash", |
|
| 62 |
- cmd: dockercmd.From, |
|
| 63 |
- replaceArgs: "rhel", |
|
| 64 |
- fileData: []byte(trSlashFile), |
|
| 65 |
- expectedOutput: expectedtrSlashFile, |
|
| 66 |
- expectedDiffs: 1, |
|
| 67 |
- expectedErr: nil, |
|
| 68 |
- }, |
|
| 69 |
- {
|
|
| 70 |
- name: "multiple trailing slashes plus plus", |
|
| 71 |
- cmd: dockercmd.From, |
|
| 72 |
- replaceArgs: "scratch", |
|
| 73 |
- fileData: []byte(trickierFile), |
|
| 74 |
- expectedOutput: expectedTrickierFile, |
|
| 75 |
- expectedDiffs: 1, |
|
| 76 |
- expectedErr: nil, |
|
| 77 |
- }, |
|
| 78 |
- } |
|
| 79 |
- |
|
| 80 |
- for _, test := range tests {
|
|
| 81 |
- out, err := replaceValidCmd(test.cmd, test.replaceArgs, test.fileData) |
|
| 82 |
- if err != test.expectedErr {
|
|
| 83 |
- t.Errorf("%s: Unexpected error: Expected %v, got %v", test.name, test.expectedErr, err)
|
|
| 84 |
- } |
|
| 85 |
- if out != test.expectedOutput {
|
|
| 86 |
- t.Errorf("%s: Unexpected output:\n\nExpected:\n%s\n(length: %d)\n\ngot:\n%s\n(length: %d)",
|
|
| 87 |
- test.name, test.expectedOutput, len(test.expectedOutput), out, len(out)) |
|
| 88 |
- } |
|
| 89 |
- } |
|
| 90 |
- |
|
| 91 |
- // Re-use the tests above |
|
| 92 |
- var buf *bytes.Buffer |
|
| 93 |
- for _, test := range tests {
|
|
| 94 |
- buf = bytes.NewBuffer([]byte(test.fileData)) |
|
| 95 |
- original, err := parser.Parse(buf) |
|
| 96 |
- if err != nil {
|
|
| 97 |
- log.Println(err) |
|
| 98 |
- } |
|
| 99 |
- repl, err := replaceValidCmd(test.cmd, test.replaceArgs, test.fileData) |
|
| 100 |
- if err != nil {
|
|
| 101 |
- log.Println(err) |
|
| 102 |
- } |
|
| 103 |
- buf = bytes.NewBuffer([]byte(repl)) |
|
| 104 |
- edited, err := parser.Parse(buf) |
|
| 105 |
- if err != nil {
|
|
| 106 |
- log.Println(err) |
|
| 107 |
- } |
|
| 108 |
- |
|
| 109 |
- diff := cmpASTs(original, edited) |
|
| 110 |
- |
|
| 111 |
- if diff != test.expectedDiffs {
|
|
| 112 |
- t.Errorf("%s: Edit mismatch, expected %d edit(s), got %d", test.name, test.expectedDiffs, diff)
|
|
| 113 |
- } |
|
| 114 |
- } |
|
| 115 |
-} |
|
| 116 |
- |
|
| 117 |
-// cmpASTs compares two Abstract Syntax Trees and returns the |
|
| 118 |
-// amount of differences between them |
|
| 119 |
-func cmpASTs(original *parser.Node, edited *parser.Node) int {
|
|
| 120 |
- index := 0 |
|
| 121 |
- if original.Value != edited.Value {
|
|
| 122 |
- index++ |
|
| 123 |
- } |
|
| 124 |
- |
|
| 125 |
- originalChildren := make([]*parser.Node, 0) |
|
| 126 |
- for _, n := range original.Children {
|
|
| 127 |
- originalChildren = append(originalChildren, n) |
|
| 128 |
- } |
|
| 129 |
- editedChildren := make([]*parser.Node, 0) |
|
| 130 |
- for _, n := range edited.Children {
|
|
| 131 |
- editedChildren = append(editedChildren, n) |
|
| 132 |
- } |
|
| 133 |
- for i := 0; i < len(editedChildren); i++ {
|
|
| 134 |
- index += cmpASTs(originalChildren[i], editedChildren[i]) |
|
| 135 |
- } |
|
| 136 |
- |
|
| 137 |
- if original.Next != nil && edited.Next != nil {
|
|
| 138 |
- index += cmpASTs(original.Next, edited.Next) |
|
| 139 |
- } else if original.Next != edited.Next {
|
|
| 140 |
- index++ |
|
| 141 |
- } |
|
| 142 |
- return index |
|
| 143 |
-} |
|
| 144 |
- |
|
| 145 |
-func TestTraverseAST(t *testing.T) {
|
|
| 146 |
- tests := []struct {
|
|
| 147 |
- name string |
|
| 148 |
- cmd string |
|
| 149 |
- fileData []byte |
|
| 150 |
- expected int |
|
| 151 |
- }{
|
|
| 152 |
- {
|
|
| 153 |
- name: "dockerFile", |
|
| 154 |
- cmd: dockercmd.Entrypoint, |
|
| 155 |
- fileData: []byte(dockerFile), |
|
| 156 |
- expected: 1, |
|
| 157 |
- }, |
|
| 158 |
- {
|
|
| 159 |
- name: "dockerFile no newline", |
|
| 160 |
- cmd: dockercmd.Entrypoint, |
|
| 161 |
- fileData: []byte(dockerFileNoNewline), |
|
| 162 |
- expected: 1, |
|
| 163 |
- }, |
|
| 164 |
- {
|
|
| 165 |
- name: "expectedFROM", |
|
| 166 |
- cmd: dockercmd.From, |
|
| 167 |
- fileData: []byte(expectedFROM), |
|
| 168 |
- expected: 2, |
|
| 169 |
- }, |
|
| 170 |
- {
|
|
| 171 |
- name: "trSlashFile", |
|
| 172 |
- cmd: dockercmd.Entrypoint, |
|
| 173 |
- fileData: []byte(trSlashFile), |
|
| 174 |
- expected: 0, |
|
| 175 |
- }, |
|
| 176 |
- {
|
|
| 177 |
- name: "expectedtrSlashFile", |
|
| 178 |
- cmd: dockercmd.Cmd, |
|
| 179 |
- fileData: []byte(expectedtrSlashFile), |
|
| 180 |
- expected: 1, |
|
| 181 |
- }, |
|
| 182 |
- } |
|
| 183 |
- |
|
| 184 |
- var buf *bytes.Buffer |
|
| 185 |
- for _, test := range tests {
|
|
| 186 |
- buf = bytes.NewBuffer([]byte(test.fileData)) |
|
| 187 |
- node, err := parser.Parse(buf) |
|
| 188 |
- if err != nil {
|
|
| 189 |
- log.Println(err) |
|
| 190 |
- } |
|
| 191 |
- |
|
| 192 |
- howMany := traverseAST(test.cmd, node) |
|
| 193 |
- if howMany != test.expected {
|
|
| 194 |
- t.Errorf("Wrong result, expected %d, got %d", test.expected, howMany)
|
|
| 195 |
- } |
|
| 196 |
- } |
|
| 197 |
-} |
|
| 198 |
- |
|
| 199 |
-func TestAppendEnvVars(t *testing.T) {
|
|
| 200 |
- tests := []struct {
|
|
| 201 |
- name string |
|
| 202 |
- dockerFile string |
|
| 203 |
- envVars map[string]string |
|
| 204 |
- expected string |
|
| 205 |
- }{
|
|
| 206 |
- {
|
|
| 207 |
- name: "regular dockerFile", |
|
| 208 |
- dockerFile: dockerFile, |
|
| 209 |
- envVars: map[string]string{"VAR1": "value1"},
|
|
| 210 |
- expected: dockerFile + "ENV VAR1=\"value1\"\n", |
|
| 211 |
- }, |
|
| 212 |
- {
|
|
| 213 |
- name: "dockerFile with no newline", |
|
| 214 |
- dockerFile: dockerFileNoNewline, |
|
| 215 |
- envVars: map[string]string{"VAR1": "value1"},
|
|
| 216 |
- expected: dockerFileNoNewline + "\nENV VAR1=\"value1\"\n", |
|
| 217 |
- }, |
|
| 218 |
- } |
|
| 219 |
- |
|
| 220 |
- for _, test := range tests {
|
|
| 221 |
- result := appendMetadata(Env, test.dockerFile, test.envVars) |
|
| 222 |
- if result != test.expected {
|
|
| 223 |
- t.Errorf("%s: unexpected result.\n\tExpected: %s\n\tGot: %s\n", test.name, test.expected, result)
|
|
| 224 |
- } |
|
| 225 |
- } |
|
| 226 |
-} |
|
| 227 |
- |
|
| 228 |
-func TestAppendLabels(t *testing.T) {
|
|
| 229 |
- tests := []struct {
|
|
| 230 |
- name string |
|
| 231 |
- dockerFile string |
|
| 232 |
- labels map[string]string |
|
| 233 |
- expected string |
|
| 234 |
- }{
|
|
| 235 |
- {
|
|
| 236 |
- name: "regular dockerFile", |
|
| 237 |
- dockerFile: dockerFile, |
|
| 238 |
- labels: map[string]string{"LABEL1": "value1"},
|
|
| 239 |
- expected: dockerFile + "LABEL LABEL1=\"value1\"\n", |
|
| 240 |
- }, |
|
| 241 |
- {
|
|
| 242 |
- name: "dockerFile with no newline", |
|
| 243 |
- dockerFile: dockerFileNoNewline, |
|
| 244 |
- labels: map[string]string{"LABEL1": "value1"},
|
|
| 245 |
- expected: dockerFileNoNewline + "\nLABEL LABEL1=\"value1\"\n", |
|
| 246 |
- }, |
|
| 247 |
- } |
|
| 248 |
- |
|
| 249 |
- for _, test := range tests {
|
|
| 250 |
- result := appendMetadata(Label, test.dockerFile, test.labels) |
|
| 251 |
- if result != test.expected {
|
|
| 252 |
- t.Errorf("%s: unexpected result.\n\tExpected: %s\n\tGot: %s\n", test.name, test.expected, result)
|
|
| 253 |
- } |
|
| 254 |
- } |
|
| 255 |
-} |
|
| 256 |
- |
|
| 257 | 14 |
func TestInsertEnvAfterFrom(t *testing.T) {
|
| 258 | 15 |
tests := map[string]struct {
|
| 259 | 16 |
original string |
| ... | ... |
@@ -321,69 +78,54 @@ RUN echo "hello world" |
| 321 | 321 |
} |
| 322 | 322 |
} |
| 323 | 323 |
|
| 324 |
-const ( |
|
| 325 |
- dockerFile = ` |
|
| 326 |
-FROM openshift/origin-base |
|
| 327 |
-FROM candidate |
|
| 328 |
- |
|
| 329 |
-RUN mkdir -p /var/lib/openshift |
|
| 330 |
- |
|
| 331 |
-ADD bin/openshift /usr/bin/openshift |
|
| 332 |
-RUN ln -s /usr/bin/openshift /usr/bin/oc && \ |
|
| 333 |
- |
|
| 334 |
-ENV HOME /root |
|
| 335 |
-WORKDIR /var/lib/openshift |
|
| 336 |
-ENTRYPOINT ["/usr/bin/openshift"] |
|
| 337 |
-` |
|
| 338 |
- dockerFileNoNewline = ` |
|
| 339 |
-FROM openshift/origin-base |
|
| 340 |
-FROM candidate |
|
| 341 |
- |
|
| 342 |
-RUN mkdir -p /var/lib/openshift |
|
| 343 |
- |
|
| 344 |
-ADD bin/openshift /usr/bin/openshift |
|
| 345 |
-RUN ln -s /usr/bin/openshift /usr/bin/oc && \ |
|
| 346 |
- |
|
| 347 |
-ENV HOME /root |
|
| 348 |
-WORKDIR /var/lib/openshift |
|
| 349 |
-ENTRYPOINT ["/usr/bin/openshift"]` |
|
| 350 |
- |
|
| 351 |
- expectedFROM = ` |
|
| 352 |
-FROM openshift/origin-base |
|
| 353 |
-FROM other/image |
|
| 354 |
- |
|
| 355 |
-RUN mkdir -p /var/lib/openshift |
|
| 356 |
- |
|
| 357 |
-ADD bin/openshift /usr/bin/openshift |
|
| 358 |
-RUN ln -s /usr/bin/openshift /usr/bin/oc && \ |
|
| 359 |
- |
|
| 360 |
-ENV HOME /root |
|
| 361 |
-WORKDIR /var/lib/openshift |
|
| 362 |
-ENTRYPOINT ["/usr/bin/openshift"] |
|
| 363 |
-` |
|
| 364 |
- |
|
| 365 |
- trSlashFile = ` |
|
| 366 |
-from \ |
|
| 367 |
-centos |
|
| 368 |
-CMD "cat /etc/passwd"` |
|
| 369 |
- |
|
| 370 |
- expectedtrSlashFile = ` |
|
| 371 |
-from \ |
|
| 372 |
-rhel |
|
| 373 |
-CMD "cat /etc/passwd"` |
|
| 374 |
- |
|
| 375 |
- trickierFile = ` |
|
| 376 |
-from centos \ |
|
| 377 |
-rhel \ |
|
| 378 |
-ubuntu |
|
| 379 |
- |
|
| 380 |
-CMD ["executable","param1","param2"] |
|
| 381 |
-` |
|
| 382 |
- |
|
| 383 |
- expectedTrickierFile = ` |
|
| 384 |
-from \ |
|
| 385 |
-scratch |
|
| 386 |
- |
|
| 387 |
-CMD ["executable","param1","param2"] |
|
| 388 |
-` |
|
| 389 |
-) |
|
| 324 |
+func TestReplaceLastFrom(t *testing.T) {
|
|
| 325 |
+ tests := []struct {
|
|
| 326 |
+ original string |
|
| 327 |
+ image string |
|
| 328 |
+ want string |
|
| 329 |
+ }{
|
|
| 330 |
+ {
|
|
| 331 |
+ original: `# no FROM instruction`, |
|
| 332 |
+ image: "centos", |
|
| 333 |
+ want: ``, |
|
| 334 |
+ }, |
|
| 335 |
+ {
|
|
| 336 |
+ original: `FROM scratch |
|
| 337 |
+# FROM busybox |
|
| 338 |
+RUN echo "hello world" |
|
| 339 |
+`, |
|
| 340 |
+ image: "centos", |
|
| 341 |
+ want: `FROM centos |
|
| 342 |
+RUN echo "hello world" |
|
| 343 |
+`, |
|
| 344 |
+ }, |
|
| 345 |
+ {
|
|
| 346 |
+ original: `FROM scratch |
|
| 347 |
+FROM busybox |
|
| 348 |
+RUN echo "hello world" |
|
| 349 |
+`, |
|
| 350 |
+ image: "centos", |
|
| 351 |
+ want: `FROM scratch |
|
| 352 |
+FROM centos |
|
| 353 |
+RUN echo "hello world" |
|
| 354 |
+`, |
|
| 355 |
+ }, |
|
| 356 |
+ } |
|
| 357 |
+ for i, test := range tests {
|
|
| 358 |
+ got, err := parser.Parse(strings.NewReader(test.original)) |
|
| 359 |
+ if err != nil {
|
|
| 360 |
+ t.Errorf("test[%d]: %v", i, err)
|
|
| 361 |
+ continue |
|
| 362 |
+ } |
|
| 363 |
+ want, err := parser.Parse(strings.NewReader(test.want)) |
|
| 364 |
+ if err != nil {
|
|
| 365 |
+ t.Errorf("test[%d]: %v", i, err)
|
|
| 366 |
+ continue |
|
| 367 |
+ } |
|
| 368 |
+ replaceLastFrom(got, test.image) |
|
| 369 |
+ if !reflect.DeepEqual(got, want) {
|
|
| 370 |
+ t.Errorf("test[%d]: replaceLastFrom(node, %+v) = %+v; want %+v", i, test.image, got, want)
|
|
| 371 |
+ t.Logf("resulting Dockerfile:\n%s", dockerfile.ParseTreeToDockerfile(got))
|
|
| 372 |
+ } |
|
| 373 |
+ } |
|
| 374 |
+} |
| ... | ... |
@@ -58,7 +58,7 @@ func (s *STIBuilder) Build() error {
|
| 58 | 58 |
DockerCfgPath: os.Getenv(dockercfg.PullAuthType), |
| 59 | 59 |
Tag: tag, |
| 60 | 60 |
ScriptsURL: s.build.Spec.Strategy.SourceStrategy.Scripts, |
| 61 |
- Environment: getBuildEnvVars(s.build), |
|
| 61 |
+ Environment: buildEnvVars(s.build), |
|
| 62 | 62 |
LabelNamespace: api.DefaultDockerLabelNamespace, |
| 63 | 63 |
Incremental: s.build.Spec.Strategy.SourceStrategy.Incremental, |
| 64 | 64 |
ForcePull: s.build.Spec.Strategy.SourceStrategy.ForcePull, |
| ... | ... |
@@ -157,3 +157,20 @@ func (s *STIBuilder) Build() error {
|
| 157 | 157 |
} |
| 158 | 158 |
return nil |
| 159 | 159 |
} |
| 160 |
+ |
|
| 161 |
+// buildEnvVars returns a map with build metadata to be inserted into Docker |
|
| 162 |
+// images produced by build. It transforms the output from buildInfo into the |
|
| 163 |
+// input format expected by stiapi.Config.Environment. |
|
| 164 |
+// Note that using a map has at least two downsides: |
|
| 165 |
+// 1. The order of metadata KeyValue pairs is lost; |
|
| 166 |
+// 2. In case of repeated Keys, the last Value takes precedence right here, |
|
| 167 |
+// instead of deferring what to do with repeated environment variables to the |
|
| 168 |
+// Docker runtime. |
|
| 169 |
+func buildEnvVars(build *api.Build) map[string]string {
|
|
| 170 |
+ bi := buildInfo(build) |
|
| 171 |
+ envVars := make(map[string]string, len(bi)) |
|
| 172 |
+ for _, item := range bi {
|
|
| 173 |
+ envVars[item.Key] = item.Value |
|
| 174 |
+ } |
|
| 175 |
+ return envVars |
|
| 176 |
+} |
| 160 | 177 |
deleted file mode 100644 |
| ... | ... |
@@ -1,30 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- buildapi "github.com/openshift/origin/pkg/build/api" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-// getBuildEnvVars returns a map with the environment variables that should be added |
|
| 8 |
-// to the built image |
|
| 9 |
-func getBuildEnvVars(build *buildapi.Build) map[string]string {
|
|
| 10 |
- envVars := map[string]string{
|
|
| 11 |
- "OPENSHIFT_BUILD_NAME": build.Name, |
|
| 12 |
- "OPENSHIFT_BUILD_NAMESPACE": build.Namespace, |
|
| 13 |
- "OPENSHIFT_BUILD_SOURCE": build.Spec.Source.Git.URI, |
|
| 14 |
- } |
|
| 15 |
- if build.Spec.Source.Git.Ref != "" {
|
|
| 16 |
- envVars["OPENSHIFT_BUILD_REFERENCE"] = build.Spec.Source.Git.Ref |
|
| 17 |
- } |
|
| 18 |
- if build.Spec.Revision != nil && |
|
| 19 |
- build.Spec.Revision.Git != nil && |
|
| 20 |
- build.Spec.Revision.Git.Commit != "" {
|
|
| 21 |
- envVars["OPENSHIFT_BUILD_COMMIT"] = build.Spec.Revision.Git.Commit |
|
| 22 |
- } |
|
| 23 |
- if build.Spec.Strategy.Type == buildapi.SourceBuildStrategyType {
|
|
| 24 |
- userEnv := build.Spec.Strategy.SourceStrategy.Env |
|
| 25 |
- for _, v := range userEnv {
|
|
| 26 |
- envVars[v.Name] = v.Value |
|
| 27 |
- } |
|
| 28 |
- } |
|
| 29 |
- return envVars |
|
| 30 |
-} |
| 31 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,84 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
- |
|
| 6 |
- "github.com/openshift/origin/pkg/build/api" |
|
| 7 |
- kapi "k8s.io/kubernetes/pkg/api" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-func TestImageTag(t *testing.T) {
|
|
| 11 |
- type tagTest struct {
|
|
| 12 |
- build api.Build |
|
| 13 |
- expected string |
|
| 14 |
- } |
|
| 15 |
- tests := []tagTest{
|
|
| 16 |
- {
|
|
| 17 |
- build: api.Build{
|
|
| 18 |
- Spec: api.BuildSpec{
|
|
| 19 |
- Output: api.BuildOutput{
|
|
| 20 |
- To: &kapi.ObjectReference{
|
|
| 21 |
- Kind: "DockerImage", |
|
| 22 |
- Name: "test/tag", |
|
| 23 |
- }, |
|
| 24 |
- }, |
|
| 25 |
- }, |
|
| 26 |
- }, |
|
| 27 |
- expected: "test/tag", |
|
| 28 |
- }, |
|
| 29 |
- {
|
|
| 30 |
- build: api.Build{
|
|
| 31 |
- Spec: api.BuildSpec{
|
|
| 32 |
- Output: api.BuildOutput{
|
|
| 33 |
- To: &kapi.ObjectReference{
|
|
| 34 |
- Kind: "DockerImage", |
|
| 35 |
- Name: "registry-server.test:5000/test/tag", |
|
| 36 |
- }, |
|
| 37 |
- }, |
|
| 38 |
- }, |
|
| 39 |
- }, |
|
| 40 |
- expected: "registry-server.test:5000/test/tag", |
|
| 41 |
- }, |
|
| 42 |
- } |
|
| 43 |
- for _, x := range tests {
|
|
| 44 |
- result := x.build.Spec.Output.To.Name |
|
| 45 |
- if result != x.expected {
|
|
| 46 |
- t.Errorf("Unexpected imageTag result. Expected: %s, Actual: %s",
|
|
| 47 |
- result, x.expected) |
|
| 48 |
- } |
|
| 49 |
- } |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-func TestGetBuildEnvVars(t *testing.T) {
|
|
| 53 |
- b := &api.Build{
|
|
| 54 |
- ObjectMeta: kapi.ObjectMeta{
|
|
| 55 |
- Name: "1234", |
|
| 56 |
- }, |
|
| 57 |
- Spec: api.BuildSpec{
|
|
| 58 |
- Source: api.BuildSource{
|
|
| 59 |
- Git: &api.GitBuildSource{
|
|
| 60 |
- URI: "github.com/build/uri", |
|
| 61 |
- Ref: "my-branch", |
|
| 62 |
- }, |
|
| 63 |
- }, |
|
| 64 |
- Revision: &api.SourceRevision{
|
|
| 65 |
- Git: &api.GitSourceRevision{
|
|
| 66 |
- Commit: "56789", |
|
| 67 |
- }, |
|
| 68 |
- }, |
|
| 69 |
- }, |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- vars := getBuildEnvVars(b) |
|
| 73 |
- expected := map[string]string{
|
|
| 74 |
- "OPENSHIFT_BUILD_NAME": "1234", |
|
| 75 |
- "OPENSHIFT_BUILD_SOURCE": "github.com/build/uri", |
|
| 76 |
- "OPENSHIFT_BUILD_REFERENCE": "my-branch", |
|
| 77 |
- "OPENSHIFT_BUILD_COMMIT": "56789", |
|
| 78 |
- } |
|
| 79 |
- for k, v := range expected {
|
|
| 80 |
- if vars[k] != v {
|
|
| 81 |
- t.Errorf("Expected: %s,%s, Got: %s,%s", k, v, k, vars[k])
|
|
| 82 |
- } |
|
| 83 |
- } |
|
| 84 |
-} |
| ... | ... |
@@ -21,6 +21,18 @@ func Env(m []KeyValue) (string, error) {
|
| 21 | 21 |
return keyValueInstruction(command.Env, m) |
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
+// From builds a FROM Dockerfile instruction referring the base image image. |
|
| 25 |
+func From(image string) (string, error) {
|
|
| 26 |
+ return unquotedArgsInstruction(command.From, image) |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+// Label builds a LABEL Dockerfile instruction from the mapping m. Keys and |
|
| 30 |
+// values are serialized as JSON strings to ensure compatibility with the |
|
| 31 |
+// Dockerfile parser. |
|
| 32 |
+func Label(m []KeyValue) (string, error) {
|
|
| 33 |
+ return keyValueInstruction(command.Label, m) |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 24 | 36 |
// keyValueInstruction builds a Dockerfile instruction from the mapping m. Keys |
| 25 | 37 |
// and values are serialized as JSON strings to ensure compatibility with the |
| 26 | 38 |
// Dockerfile parser. Syntax: |
| ... | ... |
@@ -42,3 +54,15 @@ func keyValueInstruction(cmd string, m []KeyValue) (string, error) {
|
| 42 | 42 |
} |
| 43 | 43 |
return strings.Join(s, " "), nil |
| 44 | 44 |
} |
| 45 |
+ |
|
| 46 |
+// unquotedArgsInstruction builds a Dockerfile instruction that takes unquoted |
|
| 47 |
+// string arguments. Syntax: |
|
| 48 |
+// COMMAND single unquoted argument |
|
| 49 |
+// COMMAND value1 value2 value3 ... |
|
| 50 |
+func unquotedArgsInstruction(cmd string, args ...string) (string, error) {
|
|
| 51 |
+ s := []string{strings.ToUpper(cmd)}
|
|
| 52 |
+ for _, arg := range args {
|
|
| 53 |
+ s = append(s, strings.Split(arg, "\n")...) |
|
| 54 |
+ } |
|
| 55 |
+ return strings.TrimRight(strings.Join(s, " "), " "), nil |
|
| 56 |
+} |
| ... | ... |
@@ -1,20 +1,34 @@ |
| 1 | 1 |
package dockerfile |
| 2 | 2 |
|
| 3 |
-import "testing" |
|
| 3 |
+import ( |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "testing" |
|
| 4 | 7 |
|
| 5 |
-// TestEnv tests calling Env with multiple inputs. |
|
| 6 |
-func TestEnv(t *testing.T) {
|
|
| 8 |
+ "github.com/docker/docker/builder/command" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// TestKeyValueInstructions tests calling derivatives of keyValueInstruction |
|
| 12 |
+// with multiple inputs. |
|
| 13 |
+func TestKeyValueInstructions(t *testing.T) {
|
|
| 14 |
+ keyValuesInstructions := []struct {
|
|
| 15 |
+ f func([]KeyValue) (string, error) |
|
| 16 |
+ cmd string |
|
| 17 |
+ }{
|
|
| 18 |
+ {Env, command.Env},
|
|
| 19 |
+ {Label, command.Label},
|
|
| 20 |
+ } |
|
| 7 | 21 |
testCases := []struct {
|
| 8 | 22 |
in []KeyValue |
| 9 | 23 |
want string |
| 10 | 24 |
}{
|
| 11 | 25 |
{
|
| 12 | 26 |
in: nil, |
| 13 |
- want: `ENV`, |
|
| 27 |
+ want: ``, |
|
| 14 | 28 |
}, |
| 15 | 29 |
{
|
| 16 | 30 |
in: []KeyValue{},
|
| 17 |
- want: `ENV`, |
|
| 31 |
+ want: ``, |
|
| 18 | 32 |
}, |
| 19 | 33 |
{
|
| 20 | 34 |
in: []KeyValue{
|
| ... | ... |
@@ -22,14 +36,14 @@ func TestEnv(t *testing.T) {
|
| 22 | 22 |
{"", "ABC"},
|
| 23 | 23 |
{"ABC", ""},
|
| 24 | 24 |
}, |
| 25 |
- want: `ENV ""="" ""="ABC" "ABC"=""`, |
|
| 25 |
+ want: `""="" ""="ABC" "ABC"=""`, |
|
| 26 | 26 |
}, |
| 27 | 27 |
{
|
| 28 | 28 |
in: []KeyValue{
|
| 29 | 29 |
{"GOPATH", "/go"},
|
| 30 | 30 |
{"MSG", "Hello World!"},
|
| 31 | 31 |
}, |
| 32 |
- want: `ENV "GOPATH"="/go" "MSG"="Hello World!"`, |
|
| 32 |
+ want: `"GOPATH"="/go" "MSG"="Hello World!"`, |
|
| 33 | 33 |
}, |
| 34 | 34 |
{
|
| 35 | 35 |
in: []KeyValue{
|
| ... | ... |
@@ -37,13 +51,13 @@ func TestEnv(t *testing.T) {
|
| 37 | 37 |
{"GOPATH", "/go"},
|
| 38 | 38 |
{"PATH", "$GOPATH/bin:$PATH"},
|
| 39 | 39 |
}, |
| 40 |
- want: `ENV "PATH"="/bin" "GOPATH"="/go" "PATH"="$GOPATH/bin:$PATH"`, |
|
| 40 |
+ want: `"PATH"="/bin" "GOPATH"="/go" "PATH"="$GOPATH/bin:$PATH"`, |
|
| 41 | 41 |
}, |
| 42 | 42 |
{
|
| 43 | 43 |
in: []KeyValue{
|
| 44 | 44 |
{"你好", "我会说汉语"},
|
| 45 | 45 |
}, |
| 46 |
- want: `ENV "你好"="我会说汉语"`, |
|
| 46 |
+ want: `"你好"="我会说汉语"`, |
|
| 47 | 47 |
}, |
| 48 | 48 |
{
|
| 49 | 49 |
// This tests handling an string encoding edge case. |
| ... | ... |
@@ -51,16 +65,53 @@ func TestEnv(t *testing.T) {
|
| 51 | 51 |
in: []KeyValue{
|
| 52 | 52 |
{"☃", "'\" \\ / \b \f \n \r \t \x00"},
|
| 53 | 53 |
}, |
| 54 |
- want: `ENV "☃"="'\" \\ / \u0008 \u000c \n \r \t \u0000"`, |
|
| 54 |
+ want: `"☃"="'\" \\ / \u0008 \u000c \n \r \t \u0000"`, |
|
| 55 |
+ }, |
|
| 56 |
+ } |
|
| 57 |
+ for _, tc := range testCases {
|
|
| 58 |
+ for _, kvi := range keyValuesInstructions {
|
|
| 59 |
+ got, err := kvi.f(tc.in) |
|
| 60 |
+ if err != nil {
|
|
| 61 |
+ t.Fatal(err) |
|
| 62 |
+ } |
|
| 63 |
+ want := strings.TrimRight(fmt.Sprintf("%s %s", strings.ToUpper(kvi.cmd), tc.want), " ")
|
|
| 64 |
+ if got != want {
|
|
| 65 |
+ t.Errorf("%s(%v) = %q; want %q", strings.Title(kvi.cmd), tc.in, got, want)
|
|
| 66 |
+ } |
|
| 67 |
+ } |
|
| 68 |
+ } |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+// TestFrom tests calling From with multiple inputs. |
|
| 72 |
+func TestFrom(t *testing.T) {
|
|
| 73 |
+ testCases := []struct {
|
|
| 74 |
+ in string |
|
| 75 |
+ want string |
|
| 76 |
+ }{
|
|
| 77 |
+ {
|
|
| 78 |
+ in: "", |
|
| 79 |
+ want: `FROM`, |
|
| 80 |
+ }, |
|
| 81 |
+ {
|
|
| 82 |
+ in: "centos:latest", |
|
| 83 |
+ want: `FROM centos:latest`, |
|
| 84 |
+ }, |
|
| 85 |
+ {
|
|
| 86 |
+ in: "中关村", |
|
| 87 |
+ want: `FROM 中关村`, |
|
| 88 |
+ }, |
|
| 89 |
+ {
|
|
| 90 |
+ in: "centos\nRUN rm -rf /\n\nUSER 100", |
|
| 91 |
+ want: `FROM centos RUN rm -rf / USER 100`, |
|
| 55 | 92 |
}, |
| 56 | 93 |
} |
| 57 | 94 |
for _, tc := range testCases {
|
| 58 |
- got, err := Env(tc.in) |
|
| 95 |
+ got, err := From(tc.in) |
|
| 59 | 96 |
if err != nil {
|
| 60 | 97 |
t.Fatal(err) |
| 61 | 98 |
} |
| 62 | 99 |
if got != tc.want {
|
| 63 |
- t.Errorf("Env(%v) = %q; want %q", tc.in, got, tc.want)
|
|
| 100 |
+ t.Errorf("From(%v) = %q; want %q", tc.in, got, tc.want)
|
|
| 64 | 101 |
} |
| 65 | 102 |
} |
| 66 | 103 |
} |