Skip to content

Commit fcac803

Browse files
committed
reject build requests for binary builds if not providing binary inputs
1 parent e72143c commit fcac803

File tree

8 files changed

+157
-10
lines changed

8 files changed

+157
-10
lines changed

pkg/build/api/validation/validation.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func validateCommonSpec(spec *buildapi.CommonSpec, fldPath *field.Path) field.Er
142142
s := spec.Strategy
143143

144144
if s.DockerStrategy != nil && s.JenkinsPipelineStrategy == nil && spec.Source.Git == nil && spec.Source.Binary == nil && spec.Source.Dockerfile == nil && spec.Source.Images == nil {
145-
allErrs = append(allErrs, field.Invalid(fldPath.Child("source"), spec.Source, "must provide a value for at least one of source, binary, images, or dockerfile"))
145+
allErrs = append(allErrs, field.Invalid(fldPath.Child("source"), "", "must provide a value for at least one source input(git, binary, dockerfile, images)."))
146146
}
147147

148148
allErrs = append(allErrs,
@@ -202,7 +202,6 @@ func validateSource(input *buildapi.BuildSource, isCustomStrategy, isDockerStrat
202202
allErrs = append(allErrs, validateImageSource(image, fldPath.Child("images").Index(i))...)
203203
}
204204
}
205-
206205
if isJenkinsPipelineStrategyFromRepo && input.Git == nil {
207206
allErrs = append(allErrs, field.Invalid(fldPath.Child("git"), "", "must be set when using Jenkins Pipeline strategy with Jenkinsfile from a git repo"))
208207
}

pkg/build/api/validation/validation_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestBuildValidationSuccess(t *testing.T) {
4646

4747
func checkDockerStrategyEmptySourceError(result field.ErrorList) bool {
4848
for _, err := range result {
49-
if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Field, "spec.source") && strings.Contains(err.Detail, "must provide a value for at least one of source, binary, images, or dockerfile") {
49+
if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Field, "spec.source") && strings.Contains(err.Detail, "must provide a value for at least one source input(git, binary, dockerfile, images).") {
5050
return true
5151
}
5252
}

pkg/build/controller/image_change_controller_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,16 @@ func (m *mockBuildConfigUpdater) Update(buildcfg *buildapi.BuildConfig) error {
302302
}
303303

304304
func mockBuildConfig(baseImage, triggerImage, repoName, repoTag string) *buildapi.BuildConfig {
305+
dockerfile := "FROM foo"
305306
return &buildapi.BuildConfig{
306307
ObjectMeta: kapi.ObjectMeta{
307308
Name: "testBuildCfg",
308309
},
309310
Spec: buildapi.BuildConfigSpec{
310311
CommonSpec: buildapi.CommonSpec{
312+
Source: buildapi.BuildSource{
313+
Dockerfile: &dockerfile,
314+
},
311315
Strategy: buildapi.BuildStrategy{
312316
DockerStrategy: &buildapi.DockerBuildStrategy{
313317
From: &kapi.ObjectReference{

pkg/build/generator/generator.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,8 @@ func (g *BuildGenerator) Instantiate(ctx kapi.Context, request *buildapi.BuildRe
218218
if err != nil {
219219
return nil, err
220220
}
221-
222221
if buildutil.IsPaused(bc) {
223-
return nil, errors.NewInternalError(&GeneratorFatalError{fmt.Sprintf("can't instantiate from BuildConfig %s/%s: BuildConfig is paused", bc.Namespace, bc.Name)})
222+
return nil, errors.NewBadRequest(fmt.Sprintf("can't instantiate from BuildConfig %s/%s: BuildConfig is paused", bc.Namespace, bc.Name))
224223
}
225224

226225
if err := g.checkLastVersion(bc, request.LastVersion); err != nil {
@@ -333,7 +332,6 @@ func (g *BuildGenerator) Clone(ctx kapi.Context, request *buildapi.BuildRequest)
333332
if err != nil && !errors.IsNotFound(err) {
334333
return nil, err
335334
}
336-
337335
if buildutil.IsPaused(buildConfig) {
338336
return nil, errors.NewInternalError(&GeneratorFatalError{fmt.Sprintf("can't instantiate from BuildConfig %s/%s: BuildConfig is paused", buildConfig.Namespace, buildConfig.Name)})
339337
}
@@ -366,7 +364,6 @@ func (g *BuildGenerator) createBuild(ctx kapi.Context, build *buildapi.Build) (*
366364
return nil, errors.NewConflict(buildapi.Resource("build"), build.Namespace, fmt.Errorf("Build.Namespace does not match the provided context"))
367365
}
368366
kapi.FillObjectMetaSystemFields(ctx, &build.ObjectMeta)
369-
370367
err := g.Client.CreateBuild(ctx, build)
371368
if err != nil {
372369
return nil, err
@@ -416,13 +413,15 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi.
416413
},
417414
},
418415
}
419-
420416
if binary != nil {
421417
build.Spec.Source.Git = nil
422418
build.Spec.Source.Binary = binary
423419
if build.Spec.Source.Dockerfile != nil && binary.AsFile == "Dockerfile" {
424420
build.Spec.Source.Dockerfile = nil
425421
}
422+
} else {
423+
// must explicitly set this because we copied the source values from the buildconfig.
424+
build.Spec.Source.Binary = nil
426425
}
427426

428427
build.Name = getNextBuildName(bc)
@@ -698,6 +697,8 @@ func generateBuildFromBuild(build *buildapi.Build, buildConfig *buildapi.BuildCo
698697
Config: buildCopy.Status.Config,
699698
},
700699
}
700+
// TODO remove/update this when we support cloning binary builds
701+
newBuild.Spec.Source.Binary = nil
701702
if newBuild.Annotations == nil {
702703
newBuild.Annotations = make(map[string]string)
703704
}

pkg/build/generator/generator_test.go

+102
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,25 @@ func TestInstantiate(t *testing.T) {
4848
}
4949
}
5050

51+
func TestInstantiateBinary(t *testing.T) {
52+
generator := mockBuildGenerator()
53+
build, err := generator.Instantiate(kapi.NewDefaultContext(), &buildapi.BuildRequest{Binary: &buildapi.BinaryBuildSource{}})
54+
if err != nil {
55+
t.Errorf("Unexpected error %v", err)
56+
}
57+
if build.Spec.Source.Binary == nil {
58+
t.Errorf("build should have a binary source value, has nil")
59+
}
60+
build, err = generator.Clone(kapi.NewDefaultContext(), &buildapi.BuildRequest{Binary: &buildapi.BinaryBuildSource{}})
61+
if err != nil {
62+
t.Errorf("Unexpected error %v", err)
63+
}
64+
// TODO: we should enable this flow.
65+
if build.Spec.Source.Binary != nil {
66+
t.Errorf("build should not have a binary source value, has %v", build.Spec.Source.Binary)
67+
}
68+
}
69+
5170
// TODO(agoldste): I'm not sure the intent of this test. Using the previous logic for
5271
// the generator, which would try to update the build config before creating
5372
// the build, I can see why the UpdateBuildConfigFunc is set up to return an
@@ -83,6 +102,7 @@ func TestInstantiateRetry(t *testing.T) {
83102
*/
84103

85104
func TestInstantiateDeletingError(t *testing.T) {
105+
source := mocks.MockSource()
86106
generator := BuildGenerator{Client: Client{
87107
GetBuildConfigFunc: func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) {
88108
bc := &buildapi.BuildConfig{
@@ -91,11 +111,31 @@ func TestInstantiateDeletingError(t *testing.T) {
91111
buildapi.BuildConfigPausedAnnotation: "true",
92112
},
93113
},
114+
Spec: buildapi.BuildConfigSpec{
115+
CommonSpec: buildapi.CommonSpec{
116+
Source: source,
117+
Revision: &buildapi.SourceRevision{
118+
Git: &buildapi.GitSourceRevision{
119+
Commit: "1234",
120+
},
121+
},
122+
},
123+
},
94124
}
95125
return bc, nil
96126
},
97127
GetBuildFunc: func(ctx kapi.Context, name string) (*buildapi.Build, error) {
98128
build := &buildapi.Build{
129+
Spec: buildapi.BuildSpec{
130+
CommonSpec: buildapi.CommonSpec{
131+
Source: source,
132+
Revision: &buildapi.SourceRevision{
133+
Git: &buildapi.GitSourceRevision{
134+
Commit: "1234",
135+
},
136+
},
137+
},
138+
},
99139
Status: buildapi.BuildStatus{
100140
Config: &kapi.ObjectReference{
101141
Name: "buildconfig",
@@ -115,6 +155,61 @@ func TestInstantiateDeletingError(t *testing.T) {
115155
}
116156
}
117157

158+
// TestInstantiateBinaryClear ensures that when instantiating or cloning from a buildconfig/build
159+
// that has a binary source value, the resulting build does not have a binary source value
160+
// (because the request did not include one)
161+
func TestInstantiateBinaryRemoved(t *testing.T) {
162+
generator := mockBuildGenerator()
163+
client := generator.Client.(Client)
164+
client.GetBuildConfigFunc = func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) {
165+
bc := &buildapi.BuildConfig{
166+
ObjectMeta: kapi.ObjectMeta{
167+
Annotations: map[string]string{},
168+
},
169+
Spec: buildapi.BuildConfigSpec{
170+
CommonSpec: buildapi.CommonSpec{
171+
Source: buildapi.BuildSource{
172+
Binary: &buildapi.BinaryBuildSource{},
173+
},
174+
},
175+
},
176+
}
177+
return bc, nil
178+
}
179+
client.GetBuildFunc = func(ctx kapi.Context, name string) (*buildapi.Build, error) {
180+
build := &buildapi.Build{
181+
Spec: buildapi.BuildSpec{
182+
CommonSpec: buildapi.CommonSpec{
183+
Source: buildapi.BuildSource{
184+
Binary: &buildapi.BinaryBuildSource{},
185+
},
186+
},
187+
},
188+
Status: buildapi.BuildStatus{
189+
Config: &kapi.ObjectReference{
190+
Name: "buildconfig",
191+
},
192+
},
193+
}
194+
return build, nil
195+
}
196+
197+
build, err := generator.Instantiate(kapi.NewDefaultContext(), &buildapi.BuildRequest{})
198+
if err != nil {
199+
t.Errorf("Unexpected error %v", err)
200+
}
201+
if build.Spec.Source.Binary != nil {
202+
t.Errorf("build should not have a binary source value, has %v", build.Spec.Source.Binary)
203+
}
204+
build, err = generator.Clone(kapi.NewDefaultContext(), &buildapi.BuildRequest{})
205+
if err != nil {
206+
t.Errorf("Unexpected error %v", err)
207+
}
208+
if build.Spec.Source.Binary != nil {
209+
t.Errorf("build should not have a binary source value, has %v", build.Spec.Source.Binary)
210+
}
211+
}
212+
118213
func TestInstantiateGetBuildConfigError(t *testing.T) {
119214
generator := BuildGenerator{Client: Client{
120215
GetBuildConfigFunc: func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) {
@@ -240,6 +335,7 @@ func TestInstantiateWithImageTrigger(t *testing.T) {
240335
},
241336
}
242337

338+
source := mocks.MockSource()
243339
for _, tc := range tests {
244340
bc := &buildapi.BuildConfig{
245341
Spec: buildapi.BuildConfigSpec{
@@ -252,6 +348,12 @@ func TestInstantiateWithImageTrigger(t *testing.T) {
252348
},
253349
},
254350
},
351+
Source: source,
352+
Revision: &buildapi.SourceRevision{
353+
Git: &buildapi.GitSourceRevision{
354+
Commit: "1234",
355+
},
356+
},
255357
},
256358
Triggers: tc.triggers,
257359
},

pkg/cmd/cli/cmd/startbuild.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/spf13/cobra"
2020

2121
kapi "k8s.io/kubernetes/pkg/api"
22+
kerrors "k8s.io/kubernetes/pkg/api/errors"
2223
"k8s.io/kubernetes/pkg/api/unversioned"
2324
"k8s.io/kubernetes/pkg/client/restclient"
2425
kclientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
@@ -30,7 +31,7 @@ import (
3031
cmdutil "github.com/openshift/origin/pkg/cmd/util"
3132
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
3233
"github.com/openshift/origin/pkg/generate/git"
33-
"github.com/openshift/origin/pkg/util/errors"
34+
oerrors "github.com/openshift/origin/pkg/util/errors"
3435
"github.com/openshift/source-to-image/pkg/tar"
3536
)
3637

@@ -165,6 +166,11 @@ func (o *StartBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out io.
165166
return kcmdutil.UsageError(cmd, "Must pass a name of a build config or specify build name with '--from-build' flag")
166167
}
167168

169+
if len(buildName) != 0 && (len(fromFile) != 0 || len(fromDir) != 0 || len(fromRepo) != 0) {
170+
// TODO: we should support this, it should be possible to clone a build to run again with new uploaded artifacts.
171+
// Doing so requires introducing a new clonebinary endpoint.
172+
return kcmdutil.UsageError(cmd, "Cannot use '--from-build' flag with binary builds")
173+
}
168174
o.AsBinary = len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0
169175

170176
namespace, _, err := f.DefaultNamespace()
@@ -282,10 +288,16 @@ func (o *StartBuildOptions) Run() error {
282288
}
283289
case len(o.FromBuild) > 0:
284290
if newBuild, err = o.Client.Builds(o.Namespace).Clone(request); err != nil {
291+
if isInvalidSourceInputsError(err) {
292+
return fmt.Errorf("Build %s/%s has no valid source inputs and '--from-build' cannot be used for binary builds", o.Namespace, o.Name)
293+
}
285294
return err
286295
}
287296
default:
288297
if newBuild, err = o.Client.BuildConfigs(o.Namespace).Instantiate(request); err != nil {
298+
if isInvalidSourceInputsError(err) {
299+
return fmt.Errorf("Build configuration %s/%s has no valid source inputs, if this is a binary build you must specify one of '--from-dir', '--from-repo', or '--from-file'", o.Namespace, o.Name)
300+
}
289301
return err
290302
}
291303
}
@@ -327,7 +339,7 @@ func (o *StartBuildOptions) Run() error {
327339
if err != nil {
328340
// if --wait options is set, then retry the connection to build logs
329341
// when we hit the timeout.
330-
if o.WaitForComplete && errors.IsTimeoutErr(err) {
342+
if o.WaitForComplete && oerrors.IsTimeoutErr(err) {
331343
continue
332344
}
333345
fmt.Fprintf(o.ErrOut, "error getting logs: %v\n", err)
@@ -717,3 +729,14 @@ func WaitForBuildComplete(c osclient.BuildInterface, name string) error {
717729
}
718730
}
719731
}
732+
733+
func isInvalidSourceInputsError(err error) bool {
734+
if err != nil {
735+
if statusErr, ok := err.(*kerrors.StatusError); ok {
736+
if statusErr.ErrStatus.Reason == "Invalid" && strings.Contains(statusErr.ErrStatus.Message, "spec.source") {
737+
return true
738+
}
739+
}
740+
}
741+
return false
742+
}

test/cmd/builds.sh

+3
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ started=$(oc start-build ruby-sample-build-invalidtag)
163163
os::cmd::expect_success_and_text "oc describe build ${started}" 'centos/ruby-22-centos7$'
164164
frombuild=$(oc start-build --from-build="${started}")
165165
os::cmd::expect_success_and_text "oc describe build ${frombuild}" 'centos/ruby-22-centos7$'
166+
os::cmd::expect_failure_and_text "oc start-build ruby-sample-build-invalid-tag --from-dir=. --from-build=${started}" "Cannot use '--from-build' flag with binary builds"
167+
os::cmd::expect_failure_and_text "oc start-build ruby-sample-build-invalid-tag --from-file=. --from-build=${started}" "Cannot use '--from-build' flag with binary builds"
168+
os::cmd::expect_failure_and_text "oc start-build ruby-sample-build-invalid-tag --from-repo=. --from-build=${started}" "Cannot use '--from-build' flag with binary builds"
166169
echo "start-build: ok"
167170
os::test::junit::declare_suite_end
168171

test/extended/builds/start.go

+15
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,21 @@ var _ = g.Describe("[builds][Slow] starting a build using CLI", func() {
155155
}
156156
o.Expect(err).NotTo(o.HaveOccurred())
157157
})
158+
159+
g.It("should get an error if no --from-xxxx is specified", func() {
160+
g.By("starting the build")
161+
out, err := oc.Run("start-build").Args("sample-build").Output()
162+
o.Expect(err).To(o.HaveOccurred())
163+
o.Expect(out).To(o.ContainSubstring("has no valid source inputs"))
164+
})
165+
166+
g.It("should get an error if no --from-xxxx is specified with --from-build", func() {
167+
g.By("starting the build")
168+
out, err := oc.Run("start-build").Args("sample-build", fmt.Sprintf("--from-build=%s", "sample-build-1")).Output()
169+
o.Expect(err).To(o.HaveOccurred())
170+
o.Expect(out).To(o.ContainSubstring("has no valid source inputs"))
171+
})
172+
158173
})
159174

160175
g.Describe("cancelling build started by oc start-build --wait", func() {

0 commit comments

Comments
 (0)