Browse code

Support --binary on new-build

Clayton Coleman authored on 2015/10/27 13:41:22
Showing 9 changed files
... ...
@@ -469,6 +469,7 @@ _oc_new-build()
469 469
     flags_completion=()
470 470
 
471 471
     flags+=("--allow-missing-images")
472
+    flags+=("--binary")
472 473
     flags+=("--code=")
473 474
     flags+=("--docker-image=")
474 475
     flags+=("--dockerfile=")
... ...
@@ -2277,6 +2277,7 @@ _openshift_cli_new-build()
2277 2277
     flags_completion=()
2278 2278
 
2279 2279
     flags+=("--allow-missing-images")
2280
+    flags+=("--binary")
2280 2281
     flags+=("--code=")
2281 2282
     flags+=("--docker-image=")
2282 2283
     flags+=("--dockerfile=")
... ...
@@ -49,6 +49,7 @@ You can use '%[1]s status' to check the progress.`
49 49
 // NewCmdNewBuild implements the OpenShift cli new-build command
50 50
 func NewCmdNewBuild(fullName string, f *clientcmd.Factory, in io.Reader, out io.Writer) *cobra.Command {
51 51
 	config := newcmd.NewAppConfig()
52
+	config.ExpectToBuild = true
52 53
 
53 54
 	cmd := &cobra.Command{
54 55
 		Use:        "new-build (IMAGE | IMAGESTREAM | PATH | URL ...)",
... ...
@@ -78,6 +79,7 @@ func NewCmdNewBuild(fullName string, f *clientcmd.Factory, in io.Reader, out io.
78 78
 	cmd.Flags().VarP(&config.Environment, "env", "e", "Specify key value pairs of environment variables to set into resulting image.")
79 79
 	cmd.Flags().StringVar(&config.Strategy, "strategy", "", "Specify the build strategy to use if you don't want to detect (docker|source).")
80 80
 	cmd.Flags().StringVarP(&config.Dockerfile, "dockerfile", "D", "", "Specify the contents of a Dockerfile to build directly, implies --strategy=docker. Pass '-' to read from STDIN.")
81
+	cmd.Flags().BoolVar(&config.BinaryBuild, "binary", false, "Instead of expecting a source URL, set the build to expect binary contents. Will disable triggers.")
81 82
 	cmd.Flags().BoolVar(&config.OutputDocker, "to-docker", false, "Have the build output push to a Docker repository.")
82 83
 	cmd.Flags().StringP("labels", "l", "", "Label to set in all generated resources.")
83 84
 	cmd.Flags().BoolVar(&config.AllowMissingImages, "allow-missing-images", false, "If true, indicates that referenced Docker images that cannot be found locally or in a registry should still be used.")
... ...
@@ -335,7 +335,7 @@ func (o TagOptions) RunTag() error {
335 335
 			}
336 336
 
337 337
 			target.Spec.Tags[destTag] = targetRef
338
-			msg = fmt.Sprintf("Tag %s set up to track tag %s/%s.", o.ref, o.destNamespace[i], destNameAndTag)
338
+			msg = fmt.Sprintf("Tag %s set up to track tag %s/%s.", o.ref.Exact(), o.destNamespace[i], destNameAndTag)
339 339
 		} else {
340 340
 			// The user wants to delete a spec tag.
341 341
 			if _, ok := target.Spec.Tags[destTag]; !ok {
... ...
@@ -104,6 +104,8 @@ type SourceRef struct {
104 104
 	ContextDir string
105 105
 
106 106
 	DockerfileContents string
107
+
108
+	Binary bool
107 109
 }
108 110
 
109 111
 func urlWithoutRef(url url.URL) string {
... ...
@@ -138,22 +140,23 @@ func (r *SourceRef) BuildSource() (*buildapi.BuildSource, []buildapi.BuildTrigge
138 138
 			},
139 139
 		},
140 140
 	}
141
-	var source *buildapi.BuildSource
142
-	switch {
143
-	case r.URL != nil:
144
-		source = &buildapi.BuildSource{
145
-			Type: buildapi.BuildSourceGit,
146
-			Git: &buildapi.GitBuildSource{
147
-				URI: urlWithoutRef(*r.URL),
148
-				Ref: r.Ref,
149
-			},
150
-			ContextDir: r.ContextDir,
151
-		}
152
-	case len(r.DockerfileContents) != 0:
153
-		source = &buildapi.BuildSource{
154
-			Type:       buildapi.BuildSourceDockerfile,
155
-			Dockerfile: &r.DockerfileContents,
141
+	source := &buildapi.BuildSource{}
142
+
143
+	if len(r.DockerfileContents) != 0 {
144
+		source.Type = buildapi.BuildSourceDockerfile
145
+		source.Dockerfile = &r.DockerfileContents
146
+	}
147
+	if r.URL != nil {
148
+		source.Type = buildapi.BuildSourceGit
149
+		source.Git = &buildapi.GitBuildSource{
150
+			URI: urlWithoutRef(*r.URL),
151
+			Ref: r.Ref,
156 152
 		}
153
+		source.ContextDir = r.ContextDir
154
+	}
155
+	if r.Binary {
156
+		source.Type = buildapi.BuildSourceBinary
157
+		source.Binary = &buildapi.BinaryBuildSource{}
157 158
 	}
158 159
 	return source, triggers
159 160
 }
... ...
@@ -405,9 +408,9 @@ func (r *BuildRef) BuildConfig() (*buildapi.BuildConfig, error) {
405 405
 		return nil, fmt.Errorf("unable to suggest a name for this BuildConfig from %q", r.Source.URL)
406 406
 	}
407 407
 	var source *buildapi.BuildSource
408
-	sourceTriggers := []buildapi.BuildTriggerPolicy{}
408
+	triggers := []buildapi.BuildTriggerPolicy{}
409 409
 	if r.Source != nil {
410
-		source, sourceTriggers = r.Source.BuildSource()
410
+		source, triggers = r.Source.BuildSource()
411 411
 	}
412 412
 	if source == nil {
413 413
 		source = &buildapi.BuildSource{}
... ...
@@ -421,12 +424,14 @@ func (r *BuildRef) BuildConfig() (*buildapi.BuildConfig, error) {
421 421
 	if err != nil {
422 422
 		return nil, err
423 423
 	}
424
-	configChangeTrigger := buildapi.BuildTriggerPolicy{
425
-		Type: buildapi.ConfigChangeBuildTriggerType,
426
-	}
427 424
 
428
-	triggers := append(sourceTriggers, configChangeTrigger)
429
-	triggers = append(triggers, strategyTriggers...)
425
+	if source.Binary == nil {
426
+		configChangeTrigger := buildapi.BuildTriggerPolicy{
427
+			Type: buildapi.ConfigChangeBuildTriggerType,
428
+		}
429
+		triggers = append(triggers, configChangeTrigger)
430
+		triggers = append(triggers, strategyTriggers...)
431
+	}
430 432
 
431 433
 	return &buildapi.BuildConfig{
432 434
 		ObjectMeta: kapi.ObjectMeta{
... ...
@@ -57,10 +57,13 @@ type AppConfig struct {
57 57
 
58 58
 	Dockerfile string
59 59
 
60
-	Name               string
61
-	Strategy           string
62
-	InsecureRegistry   bool
63
-	OutputDocker       bool
60
+	Name             string
61
+	Strategy         string
62
+	InsecureRegistry bool
63
+	OutputDocker     bool
64
+
65
+	ExpectToBuild      bool
66
+	BinaryBuild        bool
64 67
 	AllowMissingImages bool
65 68
 
66 69
 	AsSearch bool
... ...
@@ -197,11 +200,12 @@ func (c *AppConfig) AddArguments(args []string) []string {
197 197
 // individualSourceRepositories collects the list of SourceRepositories specified in the
198 198
 // command line that are not associated with a builder using a '~'.
199 199
 func (c *AppConfig) individualSourceRepositories() (app.SourceRepositories, error) {
200
-	first := true
201 200
 	for _, s := range c.SourceRepositories {
202
-		if repo, ok := c.refBuilder.AddSourceRepository(s); ok && first {
201
+		if repo, ok := c.refBuilder.AddSourceRepository(s); ok {
203 202
 			repo.SetContextDir(c.ContextDir)
204
-			first = false
203
+			if c.Strategy == "docker" {
204
+				repo.BuildWithDocker()
205
+			}
205 206
 		}
206 207
 	}
207 208
 	if len(c.Dockerfile) > 0 {
... ...
@@ -305,6 +309,10 @@ func (c *AppConfig) validate() (app.ComponentReferences, app.SourceRepositories,
305 305
 		errs = append(errs, fmt.Errorf("when --strategy is specified you must provide at least one source code location"))
306 306
 	}
307 307
 
308
+	if c.BinaryBuild && (len(repos) > 0 || refs.HasSource()) {
309
+		errs = append(errs, fmt.Errorf("specifying binary builds and source repositories at the same time is not allowed"))
310
+	}
311
+
308 312
 	env, duplicateEnv, envErrs := cmdutil.ParseEnvironmentArguments(c.Environment)
309 313
 	for _, s := range duplicateEnv {
310 314
 		glog.V(1).Infof("The environment variable %q was overwritten", s)
... ...
@@ -394,18 +402,43 @@ func (c *AppConfig) resolve(components app.ComponentReferences) error {
394 394
 			errs = append(errs, err)
395 395
 			continue
396 396
 		}
397
+	}
398
+	return errors.NewAggregate(errs)
399
+}
400
+
401
+// searches on all references
402
+func (c *AppConfig) search(components app.ComponentReferences) error {
403
+	errs := []error{}
404
+	for _, ref := range components {
405
+		if err := ref.Search(); err != nil {
406
+			errs = append(errs, err)
407
+			continue
408
+		}
409
+	}
410
+	return errors.NewAggregate(errs)
411
+}
412
+
413
+// inferBuildTypes infers build status and mismatches between source and docker builders
414
+func (c *AppConfig) inferBuildTypes(components app.ComponentReferences) error {
415
+	errs := []error{}
416
+	for _, ref := range components {
397 417
 		input := ref.Input()
398
-		if !input.ExpectToBuild && input.ResolvedMatch.Builder {
399
-			if c.Strategy != "docker" {
400
-				input.ExpectToBuild = true
401
-			}
418
+		// if the strategy is explicitly Docker, all repos should assume docker
419
+		if c.Strategy == "docker" && input.Uses != nil {
420
+			input.Uses.BuildWithDocker()
421
+		}
422
+
423
+		// if we are expecting build inputs, or get a build input when strategy is not docker, expect to build
424
+		if c.ExpectToBuild || (input.ResolvedMatch.Builder && c.Strategy != "docker") {
425
+			input.ExpectToBuild = true
402 426
 		}
427
+
403 428
 		switch {
404 429
 		case input.ExpectToBuild && input.ResolvedMatch.IsTemplate():
405 430
 			// TODO: harder - break the template pieces and check if source code can be attached (look for a build config, build image, etc)
406 431
 			errs = append(errs, fmt.Errorf("template with source code explicitly attached is not supported - you must either specify the template and source code separately or attach an image to the source code using the '[image]~[code]' form"))
407 432
 			continue
408
-		case input.ExpectToBuild && !input.ResolvedMatch.Builder && !input.Uses.IsDockerBuild():
433
+		case input.ExpectToBuild && !input.ResolvedMatch.Builder && input.Uses != nil && !input.Uses.IsDockerBuild():
409 434
 			if len(c.Strategy) == 0 {
410 435
 				errs = append(errs, fmt.Errorf("the resolved match %q for component %q cannot build source code - check whether this is the image you want to use, then use --strategy=source to build using source or --strategy=docker to treat this as a Docker base image and set up a layered Docker build", input.ResolvedMatch.Name, ref))
411 436
 				continue
... ...
@@ -415,18 +448,7 @@ func (c *AppConfig) resolve(components app.ComponentReferences) error {
415 415
 			continue
416 416
 		}
417 417
 	}
418
-	return errors.NewAggregate(errs)
419
-}
420 418
 
421
-// searches on all references
422
-func (c *AppConfig) search(components app.ComponentReferences) error {
423
-	errs := []error{}
424
-	for _, ref := range components {
425
-		if err := ref.Search(); err != nil {
426
-			errs = append(errs, err)
427
-			continue
428
-		}
429
-	}
430 419
 	return errors.NewAggregate(errs)
431 420
 }
432 421
 
... ...
@@ -455,10 +477,33 @@ func (c *AppConfig) ensureHasSource(components app.ComponentReferences, reposito
455 455
 				repositories[0].UsedBy(component)
456 456
 			}
457 457
 		default:
458
-			for _, component := range components {
459
-				component.Input().ExpectToBuild = false
458
+			switch {
459
+			case c.BinaryBuild && c.ExpectToBuild:
460
+				// create new "fake" binary repos for any component that doesn't already have a repo
461
+				// TODO: source repository should possibly be refactored to be an interface or a type that better reflects
462
+				//   the different types of inputs
463
+				for _, component := range components {
464
+					input := component.Input()
465
+					if input.Uses != nil {
466
+						continue
467
+					}
468
+					repo := app.NewBinarySourceRepository()
469
+					if c.Strategy == "docker" || len(c.Strategy) == 0 {
470
+						repo.BuildWithDocker()
471
+					}
472
+					input.Use(repo)
473
+					repo.UsedBy(input)
474
+					input.ExpectToBuild = true
475
+				}
476
+			case c.ExpectToBuild:
477
+				return fmt.Errorf("you must specify at least one source repository URL, provide a Dockerfile, or indicate you wish to use binary builds")
478
+			default:
479
+				for _, component := range components {
480
+					component.Input().ExpectToBuild = false
481
+				}
460 482
 			}
461 483
 		}
484
+		glog.V(4).Infof("ensureHasSource: %#v", components[0])
462 485
 	}
463 486
 	return nil
464 487
 }
... ...
@@ -512,24 +557,25 @@ func (c *AppConfig) buildPipelines(components app.ComponentReferences, environme
512 512
 	pipelines := app.PipelineGroup{}
513 513
 	names := map[string]int{}
514 514
 	for _, group := range components.Group() {
515
-		glog.V(2).Infof("found group: %#v", group)
515
+		glog.V(4).Infof("found group: %#v", group)
516 516
 		common := app.PipelineGroup{}
517 517
 		for _, ref := range group {
518 518
 			var pipeline *app.Pipeline
519 519
 			var name string
520 520
 			if ref.Input().ExpectToBuild {
521
-				glog.V(2).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses)
521
+				glog.V(4).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses)
522 522
 				input, err := app.InputImageFromMatch(ref.Input().ResolvedMatch)
523 523
 				if err != nil {
524 524
 					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
525 525
 				}
526 526
 				if !input.AsImageStream {
527
-					glog.Warningf("Could not find an ImageStream match for %q. Make sure that a Docker image with that tag is available on the node for the build to succeed.", ref.Input().ResolvedMatch.Value)
527
+					glog.Warningf("Could not find an image stream match for %q. Make sure that a Docker image with that tag is available on the node for the build to succeed.", ref.Input().ResolvedMatch.Value)
528 528
 				}
529 529
 				strategy, source, err := app.StrategyAndSourceForRepository(ref.Input().Uses, input)
530 530
 				if err != nil {
531 531
 					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
532 532
 				}
533
+
533 534
 				// Override resource names from the cli
534 535
 				name = c.Name
535 536
 				if len(name) == 0 {
... ...
@@ -546,7 +592,7 @@ func (c *AppConfig) buildPipelines(components app.ComponentReferences, environme
546 546
 				}
547 547
 
548 548
 				// Append any exposed ports from Dockerfile to input image
549
-				if ref.Input().Uses.IsDockerBuild() {
549
+				if ref.Input().Uses.IsDockerBuild() && ref.Input().Uses.Info() != nil {
550 550
 					node := ref.Input().Uses.Info().Dockerfile.AST()
551 551
 					ports := dockerfileutil.LastExposedPorts(node)
552 552
 					if len(ports) > 0 {
... ...
@@ -564,8 +610,9 @@ func (c *AppConfig) buildPipelines(components app.ComponentReferences, environme
564 564
 				if pipeline, err = app.NewBuildPipeline(ref.Input().String(), input, c.OutputDocker, strategy, c.GetBuildEnvironment(environment), source); err != nil {
565 565
 					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
566 566
 				}
567
+
567 568
 			} else {
568
-				glog.V(2).Infof("will include %q", ref)
569
+				glog.V(4).Infof("will include %q", ref)
569 570
 				input, err := app.InputImageFromMatch(ref.Input().ResolvedMatch)
570 571
 				if err != nil {
571 572
 					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
... ...
@@ -587,6 +634,7 @@ func (c *AppConfig) buildPipelines(components app.ComponentReferences, environme
587 587
 					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
588 588
 				}
589 589
 			}
590
+
590 591
 			if err := pipeline.NeedsDeployment(environment, c.Labels, name); err != nil {
591 592
 				return nil, fmt.Errorf("can't set up a deployment for %q: %v", ref.Input(), err)
592 593
 			}
... ...
@@ -802,19 +850,26 @@ func (c *AppConfig) run(acceptors app.Acceptors) (*AppResult, error) {
802 802
 	if err != nil {
803 803
 		return nil, err
804 804
 	}
805
+
805 806
 	if err := c.resolve(components); err != nil {
806 807
 		return nil, err
807 808
 	}
808 809
 
810
+	if err := c.inferBuildTypes(components); err != nil {
811
+		return nil, err
812
+	}
813
+
809 814
 	// Couple source with resolved builder components if possible
810 815
 	if err := c.ensureHasSource(components.NeedsSource(), repositories.NotUsed()); err != nil {
811 816
 		return nil, err
812 817
 	}
818
+
813 819
 	// For source repos that are not yet coupled with a component, create components
814 820
 	sourceComponents, err := c.componentsForRepos(repositories.NotUsed())
815 821
 	if err != nil {
816 822
 		return nil, err
817 823
 	}
824
+
818 825
 	// resolve the source repo components
819 826
 	if err := c.resolve(sourceComponents); err != nil {
820 827
 		return nil, err
... ...
@@ -67,6 +67,11 @@ func (r ComponentReferences) filter(filterFunc func(ref ComponentReference) bool
67 67
 	return refs
68 68
 }
69 69
 
70
+// HasSource returns true if there is more than one component that has a repo associated
71
+func (r ComponentReferences) HasSource() bool {
72
+	return len(r.filter(func(ref ComponentReference) bool { return ref.Input().Uses != nil })) > 0
73
+}
74
+
70 75
 // NeedsSource returns all the components that need source code in order to build
71 76
 func (r ComponentReferences) NeedsSource() (refs ComponentReferences) {
72 77
 	return r.filter(func(ref ComponentReference) bool {
... ...
@@ -82,6 +82,7 @@ type SourceRepository struct {
82 82
 	usedBy           []ComponentReference
83 83
 	buildWithDocker  bool
84 84
 	ignoreRepository bool
85
+	binary           bool
85 86
 }
86 87
 
87 88
 // NewSourceRepository creates a reference to a local or remote source code repository from
... ...
@@ -114,6 +115,15 @@ func NewSourceRepositoryForDockerfile(contents string) (*SourceRepository, error
114 114
 	}, nil
115 115
 }
116 116
 
117
+// NewBinarySourceRepository creates a source repository that is configured for binary
118
+// input.
119
+func NewBinarySourceRepository() *SourceRepository {
120
+	return &SourceRepository{
121
+		binary:           true,
122
+		ignoreRepository: true,
123
+	}
124
+}
125
+
117 126
 // UsedBy sets up which component uses the source repository
118 127
 func (r *SourceRepository) UsedBy(ref ComponentReference) {
119 128
 	r.usedBy = append(r.usedBy, ref)
... ...
@@ -345,22 +355,22 @@ func StrategyAndSourceForRepository(repo *SourceRepository, image *ImageRef) (*B
345 345
 		Base:          image,
346 346
 		IsDockerBuild: repo.IsDockerBuild(),
347 347
 	}
348
-	var source *SourceRef
349
-	switch {
350
-	case repo.ignoreRepository && repo.Info() != nil && repo.Info().Dockerfile != nil:
351
-		source = &SourceRef{
352
-			DockerfileContents: repo.Info().Dockerfile.Contents(),
353
-		}
354
-	default:
348
+	source := &SourceRef{
349
+		Binary: repo.binary,
350
+	}
351
+
352
+	if repo.ignoreRepository && repo.Info() != nil && repo.Info().Dockerfile != nil {
353
+		source.DockerfileContents = repo.Info().Dockerfile.Contents()
354
+	}
355
+	if !repo.ignoreRepository {
355 356
 		remoteURL, err := repo.RemoteURL()
356 357
 		if err != nil {
357 358
 			return nil, nil, fmt.Errorf("cannot obtain remote URL for repository at %s", repo.location)
358 359
 		}
359
-		source = &SourceRef{
360
-			URL:        remoteURL,
361
-			Ref:        remoteURL.Fragment,
362
-			ContextDir: repo.ContextDir(),
363
-		}
360
+		source.URL = remoteURL
361
+		source.Ref = remoteURL.Fragment
362
+		source.ContextDir = repo.ContextDir()
364 363
 	}
364
+
365 365
 	return strategy, source, nil
366 366
 }
... ...
@@ -82,6 +82,12 @@ oc delete template ruby-helloworld-sample
82 82
 oc new-app https://github.com/openshift/ruby-hello-world -l app=ruby
83 83
 oc delete all -l app=ruby
84 84
 
85
+# check new-build
86
+[ "$(oc new-build mysql -o yaml 2>&1 | grep -F 'you must specify at least one source repository URL')" ]
87
+[ "$(oc new-build mysql --binary -o yaml | grep -F 'type: Binary')" ]
88
+[ "$(oc new-build mysql https://github.com/openshift/ruby-hello-world --strategy=docker -o yaml | grep -F 'type: Docker')" ]
89
+[ "$(oc new-build mysql https://github.com/openshift/ruby-hello-world --binary 2>&1 | grep -F 'specifying binary builds and source repositories at the same time is not allowed')" ]
90
+
85 91
 # do not allow use of non-existent image (should fail)
86 92
 [ "$(oc new-app  openshift/bogusImage https://github.com/openshift/ruby-hello-world.git -o yaml 2>&1 | grep "no image or template matched")" ]
87 93
 # allow use of non-existent image (should succeed)