Skip to content

Commit 953814c

Browse files
authoredFeb 8, 2018
Merge pull request #18272 from gabemontero/new-app-bld-msgs
Automatic merge from submit-queue (batch tested with PRs 18454, 18504, 18510, 18481, 18272). adjust newapp/newbuild error messages (arg classification vs. actual … …processing Fixes #17925 @openshift/sig-developer-experience ptal Now produces: ``` gmontero ~/go/src/github.com/openshift/origin (new-app-bld-msgs)$ oc new-build --name imagesourcetest python~https://github.com/openshift-katacoda/blog-django-py --source-image xxx --source-image-path=yyy --dry-run error: unable to locate resource for "xxx" The 'oc new-build' command will match arguments to the following types: 1. Images tagged into image streams in the current project or the 'openshift' project - if you don't specify a tag, we'll add ':latest' 2. Images in the Docker Hub, on remote registries, or on the local Docker engine 3. Git repository URLs or local paths that point to Git repositories --allow-missing-images can be used to force the use of an image that was not matched See 'oc new-build -h' for examples. ```
2 parents ebb0635 + 2785d75 commit 953814c

14 files changed

+241
-60
lines changed
 

‎pkg/oc/cli/cmd/newapp.go

+59-21
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,29 @@ func CompleteAppConfig(config *newcmd.AppConfig, f *clientcmd.Factory, c *cobra.
605605

606606
unknown := config.AddArguments(args)
607607
if len(unknown) != 0 {
608-
return kcmdutil.UsageErrorf(c, "Did not recognize the following arguments: %v", unknown)
608+
buf := &bytes.Buffer{}
609+
fmt.Fprintf(buf, "Did not recognize the following arguments: %v\n\n", unknown)
610+
for _, argName := range unknown {
611+
fmt.Fprintf(buf, "%s:\n", argName)
612+
for _, classErr := range config.EnvironmentClassificationErrors {
613+
if classErr.Value != nil {
614+
fmt.Fprintf(buf, fmt.Sprintf("%s: %v\n", classErr.Key, classErr.Value))
615+
} else {
616+
fmt.Fprintf(buf, fmt.Sprintf("%s\n", classErr.Key))
617+
}
618+
}
619+
for _, classErr := range config.SourceClassificationErrors {
620+
fmt.Fprintf(buf, fmt.Sprintf("%s: %v\n", classErr.Key, classErr.Value))
621+
}
622+
for _, classErr := range config.TemplateClassificationErrors {
623+
fmt.Fprintf(buf, fmt.Sprintf("%s: %v\n", classErr.Key, classErr.Value))
624+
}
625+
for _, classErr := range config.ComponentClassificationErrors {
626+
fmt.Fprintf(buf, fmt.Sprintf("%s: %v\n", classErr.Key, classErr.Value))
627+
}
628+
fmt.Fprintln(buf)
629+
}
630+
return kcmdutil.UsageErrorf(c, heredoc.Docf(buf.String()))
609631
}
610632

611633
if config.AllowMissingImages && config.AsSearch {
@@ -701,7 +723,7 @@ func retryBuildConfig(info *resource.Info, err error) runtime.Object {
701723
return nil
702724
}
703725

704-
func handleError(err error, baseName, commandName, commandPath string, config *newcmd.AppConfig, transformError func(err error, baseName, commandName, commandPath string, groups errorGroups)) error {
726+
func handleError(err error, baseName, commandName, commandPath string, config *newcmd.AppConfig, transformError func(err error, baseName, commandName, commandPath string, groups errorGroups, config *newcmd.AppConfig)) error {
705727
if err == nil {
706728
return nil
707729
}
@@ -711,23 +733,19 @@ func handleError(err error, baseName, commandName, commandPath string, config *n
711733
}
712734
groups := errorGroups{}
713735
for _, err := range errs {
714-
transformError(err, baseName, commandName, commandPath, groups)
736+
transformError(err, baseName, commandName, commandPath, groups, config)
715737
}
716738
buf := &bytes.Buffer{}
717-
if len(config.ArgumentClassificationErrors) > 0 {
718-
fmt.Fprintf(buf, "Errors occurred while determining argument types:\n")
719-
for _, classErr := range config.ArgumentClassificationErrors {
720-
fmt.Fprintf(buf, fmt.Sprintf("\n%s: %v\n", classErr.Key, classErr.Value))
721-
}
722-
fmt.Fprint(buf, "\n")
723-
// this print serves as a header for the printing of the errorGroups, but
724-
// only print it if we precede with classification errors, to help distinguish
725-
// between the two
726-
fmt.Fprintln(buf, "Errors occurred during resource creation:")
727-
}
728739
for _, group := range groups {
729740
fmt.Fprint(buf, kcmdutil.MultipleErrors("error: ", group.errs))
741+
if len(group.classification) > 0 {
742+
fmt.Fprintln(buf)
743+
}
744+
fmt.Fprintf(buf, group.classification)
730745
if len(group.suggestion) > 0 {
746+
if len(group.classification) > 0 {
747+
fmt.Fprintln(buf)
748+
}
731749
fmt.Fprintln(buf)
732750
}
733751
fmt.Fprint(buf, group.suggestion)
@@ -736,20 +754,22 @@ func handleError(err error, baseName, commandName, commandPath string, config *n
736754
}
737755

738756
type errorGroup struct {
739-
errs []error
740-
suggestion string
757+
errs []error
758+
suggestion string
759+
classification string
741760
}
742761
type errorGroups map[string]errorGroup
743762

744-
func (g errorGroups) Add(group string, suggestion string, err error, errs ...error) {
763+
func (g errorGroups) Add(group string, suggestion string, classification string, err error, errs ...error) {
745764
all := g[group]
746765
all.errs = append(all.errs, errs...)
747766
all.errs = append(all.errs, err)
748767
all.suggestion = suggestion
768+
all.classification = classification
749769
g[group] = all
750770
}
751771

752-
func transformRunError(err error, baseName, commandName, commandPath string, groups errorGroups) {
772+
func transformRunError(err error, baseName, commandName, commandPath string, groups errorGroups, config *newcmd.AppConfig) {
753773
switch t := err.(type) {
754774
case newcmd.ErrRequiresExplicitAccess:
755775
if t.Input.Token != nil && t.Input.Token.ServiceAccount {
@@ -762,6 +782,7 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
762782
You can see more information about the image by adding the --dry-run flag.
763783
If you trust the provided image, include the flag --grant-install-rights.`,
764784
),
785+
"",
765786
fmt.Errorf("installing %q requires an 'installer' service account with project editor access", t.Match.Value),
766787
)
767788
} else {
@@ -774,11 +795,19 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
774795
You can see more information about the image by adding the --dry-run flag.
775796
If you trust the provided image, include the flag --grant-install-rights.`,
776797
),
798+
"",
777799
fmt.Errorf("installing %q requires that you grant the image access to run with your credentials", t.Match.Value),
778800
)
779801
}
780802
return
781803
case newapp.ErrNoMatch:
804+
classification, _ := config.ClassificationWinners[t.Value]
805+
if classification.IncludeGitErrors {
806+
notGitRepo, ok := config.SourceClassificationErrors[t.Value]
807+
if ok {
808+
t.Errs = append(t.Errs, notGitRepo.Value)
809+
}
810+
}
782811
groups.Add(
783812
"no-matches",
784813
heredoc.Docf(`
@@ -794,11 +823,13 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
794823
795824
See '%[1]s -h' for examples.`, commandPath,
796825
),
826+
heredoc.Docf(classification.String()),
797827
t,
798828
t.Errs...,
799829
)
800830
return
801831
case newapp.ErrMultipleMatches:
832+
classification, _ := config.ClassificationWinners[t.Value]
802833
buf := &bytes.Buffer{}
803834
for i, match := range t.Matches {
804835

@@ -812,6 +843,7 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
812843
813844
%[2]sTo view a full list of matches, use '%[3]s %[4]s -S %[1]s'`, t.Value, buf.String(), baseName, commandName,
814845
),
846+
classification.String(),
815847
t,
816848
t.Errs...,
817849
)
@@ -830,11 +862,13 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
830862
831863
%[2]s`, t.Value, buf.String(),
832864
),
865+
classification.String(),
833866
t,
834867
t.Errs...,
835868
)
836869
return
837870
case newapp.ErrPartialMatch:
871+
classification, _ := config.ClassificationWinners[t.Value]
838872
buf := &bytes.Buffer{}
839873
fmt.Fprintf(buf, "* %s\n", t.Match.Description)
840874
fmt.Fprintf(buf, " Use %[1]s to specify this image or template\n\n", t.Match.Argument)
@@ -846,11 +880,13 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
846880
847881
%[2]s`, t.Value, buf.String(),
848882
),
883+
classification.String(),
849884
t,
850885
t.Errs...,
851886
)
852887
return
853888
case newapp.ErrNoTagsFound:
889+
classification, _ := config.ClassificationWinners[t.Value]
854890
buf := &bytes.Buffer{}
855891
fmt.Fprintf(buf, " Use --allow-missing-imagestream-tags to use this image stream\n\n")
856892
groups.Add(
@@ -860,6 +896,7 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
860896
861897
%[2]s`, t.Match.Name, buf.String(),
862898
),
899+
classification.String(),
863900
t,
864901
t.Errs...,
865902
)
@@ -868,13 +905,14 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro
868905
switch err {
869906
case errNoTokenAvailable:
870907
// TODO: improve by allowing token generation
871-
groups.Add("", "", fmt.Errorf("to install components you must be logged in with an OAuth token (instead of only a certificate)"))
908+
groups.Add("", "", "", fmt.Errorf("to install components you must be logged in with an OAuth token (instead of only a certificate)"))
872909
case newcmd.ErrNoInputs:
873910
// TODO: suggest things to the user
874-
groups.Add("", "", usageError(commandPath, newAppNoInput, baseName, commandName))
911+
groups.Add("", "", "", usageError(commandPath, newAppNoInput, baseName, commandName))
875912
default:
876-
groups.Add("", "", err)
913+
groups.Add("", "", "", err)
877914
}
915+
return
878916
}
879917

880918
func usageError(commandPath, format string, args ...interface{}) error {

‎pkg/oc/cli/cmd/newbuild.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,16 @@ func (o *NewBuildOptions) RunNewBuild() error {
213213
return nil
214214
}
215215

216-
func transformBuildError(err error, baseName, commandName, commandPath string, groups errorGroups) {
216+
func transformBuildError(err error, baseName, commandName, commandPath string, groups errorGroups, config *newcmd.AppConfig) {
217217
switch t := err.(type) {
218218
case newapp.ErrNoMatch:
219+
classification, _ := config.ClassificationWinners[t.Value]
220+
if classification.IncludeGitErrors {
221+
notGitRepo, ok := config.SourceClassificationErrors[t.Value]
222+
if ok {
223+
t.Errs = append(t.Errs, notGitRepo.Value)
224+
}
225+
}
219226
groups.Add(
220227
"no-matches",
221228
heredoc.Docf(`
@@ -230,15 +237,17 @@ func transformBuildError(err error, baseName, commandName, commandPath string, g
230237
231238
See '%[1]s -h' for examples.`, commandPath,
232239
),
240+
classification.String(),
233241
t,
234242
t.Errs...,
235243
)
236244
return
237245
}
238246
switch err {
239247
case newcmd.ErrNoInputs:
240-
groups.Add("", "", usageError(commandPath, newBuildNoInput, baseName, commandName))
248+
groups.Add("", "", "", usageError(commandPath, newBuildNoInput, baseName, commandName))
241249
return
242250
}
243-
transformRunError(err, baseName, commandName, commandPath, groups)
251+
transformRunError(err, baseName, commandName, commandPath, groups, config)
252+
return
244253
}

‎pkg/oc/cli/cmd/newbuild_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ type MockSearcher struct {
108108
OnSearch func(precise bool, terms ...string) (app.ComponentMatches, []error)
109109
}
110110

111+
func (m MockSearcher) Type() string {
112+
return ""
113+
}
114+
111115
// Search mocks a search.
112116
func (m MockSearcher) Search(precise bool, terms ...string) (app.ComponentMatches, []error) {
113117
return m.OnSearch(precise, terms...)

‎pkg/oc/generate/app/componentresolvers.go

+25-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package app
22

33
import (
44
"sort"
5+
"strings"
56

67
"github.com/golang/glog"
78

@@ -24,6 +25,7 @@ type Resolver interface {
2425
// matches
2526
type Searcher interface {
2627
Search(precise bool, terms ...string) (ComponentMatches, []error)
28+
Type() string
2729
}
2830

2931
// WeightedResolver is a resolver identified as exact or not, depending on its weight
@@ -42,6 +44,7 @@ type PerfectMatchWeightedResolver []WeightedResolver
4244
// Resolve resolves the provided input and returns only exact matches
4345
func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, error) {
4446
var errs []error
47+
types := []string{}
4548
candidates := ScoredComponentMatches{}
4649
var group MultiSimpleSearcher
4750
var groupWeight float32 = 0.0
@@ -59,6 +62,7 @@ func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, er
5962
glog.V(5).Infof("Error from resolver: %v\n", err)
6063
errs = append(errs, err...)
6164
}
65+
types = append(types, group.Type())
6266

6367
sort.Sort(ScoredComponentMatches(matches))
6468
if len(matches) > 0 && matches[0].Score == 0.0 && (len(matches) == 1 || matches[1].Score != 0.0) {
@@ -75,7 +79,7 @@ func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, er
7579

7680
switch len(candidates) {
7781
case 0:
78-
return nil, ErrNoMatch{Value: value, Errs: errs}
82+
return nil, ErrNoMatch{Value: value, Errs: errs, Type: strings.Join(types, ", ")}
7983
case 1:
8084
if candidates[0].Score != 0.0 {
8185
if candidates[0].NoTagsFound {
@@ -108,7 +112,7 @@ type FirstMatchResolver struct {
108112
func (r FirstMatchResolver) Resolve(value string) (*ComponentMatch, error) {
109113
matches, err := r.Searcher.Search(true, value)
110114
if len(matches) == 0 {
111-
return nil, ErrNoMatch{Value: value, Errs: err}
115+
return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()}
112116
}
113117
return matches[0], errors.NewAggregate(err)
114118
}
@@ -125,7 +129,7 @@ type HighestScoreResolver struct {
125129
func (r HighestScoreResolver) Resolve(value string) (*ComponentMatch, error) {
126130
matches, err := r.Searcher.Search(true, value)
127131
if len(matches) == 0 {
128-
return nil, ErrNoMatch{Value: value, Errs: err}
132+
return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()}
129133
}
130134
sort.Sort(ScoredComponentMatches(matches))
131135
return matches[0], errors.NewAggregate(err)
@@ -146,7 +150,7 @@ func (r HighestUniqueScoreResolver) Resolve(value string) (*ComponentMatch, erro
146150
sort.Sort(ScoredComponentMatches(matches))
147151
switch len(matches) {
148152
case 0:
149-
return nil, ErrNoMatch{Value: value, Errs: err}
153+
return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()}
150154
case 1:
151155
return matches[0], errors.NewAggregate(err)
152156
default:
@@ -184,7 +188,7 @@ func (r UniqueExactOrInexactMatchResolver) Resolve(value string) (*ComponentMatc
184188
inexact := matches.Inexact()
185189
switch len(inexact) {
186190
case 0:
187-
return nil, ErrNoMatch{Value: value, Errs: err}
191+
return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()}
188192
case 1:
189193
return inexact[0], errors.NewAggregate(err)
190194
default:
@@ -213,6 +217,14 @@ func (r PipelineResolver) Resolve(value string) (*ComponentMatch, error) {
213217
// MultiSimpleSearcher is a set of searchers
214218
type MultiSimpleSearcher []Searcher
215219

220+
func (s MultiSimpleSearcher) Type() string {
221+
t := []string{}
222+
for _, searcher := range s {
223+
t = append(t, searcher.Type())
224+
}
225+
return strings.Join(t, ", ")
226+
}
227+
216228
// Search searches using all searchers it holds
217229
func (s MultiSimpleSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
218230
var errs []error
@@ -239,6 +251,14 @@ type WeightedSearcher struct {
239251
// priority in search results
240252
type MultiWeightedSearcher []WeightedSearcher
241253

254+
func (s MultiWeightedSearcher) Type() string {
255+
t := []string{}
256+
for _, searcher := range s {
257+
t = append(t, searcher.Type())
258+
}
259+
return strings.Join(t, ", ")
260+
}
261+
242262
// Search searches using all searchers it holds and score according to searcher height
243263
func (s MultiWeightedSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
244264
componentMatches := ComponentMatches{}

‎pkg/oc/generate/app/componentresolvers_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ type mockSearcher struct {
99
numResults int
1010
}
1111

12+
func (m mockSearcher) Type() string {
13+
return ""
14+
}
15+
1216
func (m mockSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
1317
results := ComponentMatches{}
1418
for i := 0; i < m.numResults; i++ {

‎pkg/oc/generate/app/dockerimagelookup.go

+16
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ type DockerClientSearcher struct {
4141
AllowMissingImages bool
4242
}
4343

44+
func (r DockerClientSearcher) Type() string {
45+
return "local docker images"
46+
}
47+
4448
// Search searches all images in local docker server for images that match terms
4549
func (r DockerClientSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
4650
componentMatches := ComponentMatches{}
@@ -164,6 +168,10 @@ func (r DockerClientSearcher) Search(precise bool, terms ...string) (ComponentMa
164168
type MissingImageSearcher struct {
165169
}
166170

171+
func (r MissingImageSearcher) Type() string {
172+
return "images not found in docker registry nor locally"
173+
}
174+
167175
// Search always returns an exact match for the search terms.
168176
func (r MissingImageSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
169177
componentMatches := ComponentMatches{}
@@ -184,6 +192,10 @@ type ImageImportSearcher struct {
184192
Fallback Searcher
185193
}
186194

195+
func (s ImageImportSearcher) Type() string {
196+
return "images via the image stream import API"
197+
}
198+
187199
// Search invokes the new ImageStreamImport API to have the server look up Docker images for the user,
188200
// using secrets stored on the server.
189201
func (s ImageImportSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
@@ -267,6 +279,10 @@ type DockerRegistrySearcher struct {
267279
AllowInsecure bool
268280
}
269281

282+
func (r DockerRegistrySearcher) Type() string {
283+
return "images in the docker registry"
284+
}
285+
270286
// Search searches in the Docker registry for images that match terms
271287
func (r DockerRegistrySearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
272288
componentMatches := ComponentMatches{}

‎pkg/oc/generate/app/dockerimagelookup_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ type fakeRegistrySearcher struct {
1212
errs []error
1313
}
1414

15+
func (f fakeRegistrySearcher) Type() string {
16+
return ""
17+
}
18+
1519
func (f fakeRegistrySearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
1620
return f.matches, f.errs
1721
}

‎pkg/oc/generate/app/errors.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import (
1111
// given component.
1212
type ErrNoMatch struct {
1313
Value string
14+
Type string
1415
Qualifier string
1516
Errs []error
1617
}
1718

1819
func (e ErrNoMatch) Error() string {
1920
if len(e.Qualifier) != 0 {
20-
return fmt.Sprintf("no match for %q: %s", e.Value, e.Qualifier)
21+
return fmt.Sprintf("unable to locate any %s with name %q: %s", e.Type, e.Value, e.Qualifier)
2122
}
22-
return fmt.Sprintf("no match for %q", e.Value)
23+
return fmt.Sprintf("unable to locate any %s with name %q", e.Type, e.Value)
2324
}
2425

2526
// Suggestion is the usage error message returned when no match is found.

‎pkg/oc/generate/app/imagestreamlookup.go

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ type ImageStreamSearcher struct {
2020
AllowMissingTags bool
2121
}
2222

23+
func (r ImageStreamSearcher) Type() string {
24+
return "images in image streams"
25+
}
26+
2327
// Search will attempt to find imagestreams with names that match the passed in value
2428
func (r ImageStreamSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
2529
componentMatches := ComponentMatches{}
@@ -362,6 +366,10 @@ func (r *ImageStreamByAnnotationSearcher) annotationMatches(stream *imageapi.Ima
362366
return matches
363367
}
364368

369+
func (r *ImageStreamByAnnotationSearcher) Type() string {
370+
return "image stream images with a 'supports' annotation"
371+
}
372+
365373
// Search finds image stream images using their 'supports' annotation
366374
func (r *ImageStreamByAnnotationSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
367375
matches := ComponentMatches{}

‎pkg/oc/generate/app/templatelookup.go

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ type TemplateSearcher struct {
2525
StopOnExactMatch bool
2626
}
2727

28+
func (r TemplateSearcher) Type() string {
29+
return "templates loaded in accessible projects"
30+
}
31+
2832
// Search searches for a template and returns matches with the object representation
2933
func (r TemplateSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
3034
matches := ComponentMatches{}
@@ -104,6 +108,10 @@ type TemplateFileSearcher struct {
104108
Namespace string
105109
}
106110

111+
func (r *TemplateFileSearcher) Type() string {
112+
return "template files"
113+
}
114+
107115
// Search attempts to read template files and transform it into template objects
108116
func (r *TemplateFileSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) {
109117
matches := ComponentMatches{}

‎pkg/oc/generate/cmd/newapp.go

+57-17
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,31 @@ type AppConfig struct {
133133

134134
OriginNamespace string
135135

136-
ArgumentClassificationErrors []ArgumentClassificationError
136+
EnvironmentClassificationErrors map[string]ArgumentClassificationError
137+
SourceClassificationErrors map[string]ArgumentClassificationError
138+
TemplateClassificationErrors map[string]ArgumentClassificationError
139+
ComponentClassificationErrors map[string]ArgumentClassificationError
140+
ClassificationWinners map[string]ArgumentClassificationWinner
137141
}
138142

139143
type ArgumentClassificationError struct {
140144
Key string
141145
Value error
142146
}
143147

148+
type ArgumentClassificationWinner struct {
149+
Name string
150+
Suffix string
151+
IncludeGitErrors bool
152+
}
153+
154+
func (w *ArgumentClassificationWinner) String() string {
155+
if len(w.Name) == 0 || len(w.Suffix) == 0 {
156+
return ""
157+
}
158+
return fmt.Sprintf("Argument '%s' was classified as %s.", w.Name, w.Suffix)
159+
}
160+
144161
type ErrRequiresExplicitAccess struct {
145162
Match app.ComponentMatch
146163
Input app.GeneratorInput
@@ -181,6 +198,11 @@ func NewAppConfig() *AppConfig {
181198
JenkinsfileTester: jenkinsfile.NewTester(),
182199
},
183200
},
201+
EnvironmentClassificationErrors: map[string]ArgumentClassificationError{},
202+
SourceClassificationErrors: map[string]ArgumentClassificationError{},
203+
TemplateClassificationErrors: map[string]ArgumentClassificationError{},
204+
ComponentClassificationErrors: map[string]ArgumentClassificationError{},
205+
ClassificationWinners: map[string]ArgumentClassificationWinner{},
184206
}
185207
}
186208

@@ -249,6 +271,11 @@ func (c *AppConfig) tryToAddEnvironmentArguments(s string) bool {
249271
if rc {
250272
glog.V(2).Infof("treating %s as possible environment argument\n", s)
251273
c.Environment = append(c.Environment, s)
274+
} else {
275+
c.EnvironmentClassificationErrors[s] = ArgumentClassificationError{
276+
Key: "is not an environment variable",
277+
Value: nil,
278+
}
252279
}
253280
return rc
254281
}
@@ -263,18 +290,21 @@ func (c *AppConfig) tryToAddSourceArguments(s string) bool {
263290
return true
264291
}
265292

293+
// will combine multiple errors into one line / string
294+
errStr := ""
266295
if rerr != nil {
267-
c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{
268-
Key: fmt.Sprintf("%s as a Git repository URL", s),
269-
Value: rerr,
270-
})
296+
errStr = fmt.Sprintf("git ls-remote failed with: %v", rerr)
271297
}
272298

273299
if derr != nil {
274-
c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{
275-
Key: fmt.Sprintf("%s as a local directory pointing to a Git repository", s),
276-
Value: derr,
277-
})
300+
if len(errStr) > 0 {
301+
errStr = errStr + "; "
302+
}
303+
errStr = fmt.Sprintf("%s local file access failed with: %v", errStr, derr)
304+
}
305+
c.SourceClassificationErrors[s] = ArgumentClassificationError{
306+
Key: "is not a Git repository",
307+
Value: fmt.Errorf(errStr),
278308
}
279309

280310
return false
@@ -287,10 +317,10 @@ func (c *AppConfig) tryToAddComponentArguments(s string) bool {
287317
c.Components = append(c.Components, s)
288318
return true
289319
}
290-
c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{
291-
Key: fmt.Sprintf("%s as a template loaded in an accessible project, an imagestream tag, or a docker image reference", s),
320+
c.ComponentClassificationErrors[s] = ArgumentClassificationError{
321+
Key: "is not an image reference, image~source reference, nor template loaded in an accessible project",
292322
Value: err,
293-
})
323+
}
294324

295325
return false
296326
}
@@ -303,28 +333,38 @@ func (c *AppConfig) tryToAddTemplateArguments(s string) bool {
303333
return true
304334
}
305335
if err != nil {
306-
c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{
307-
Key: fmt.Sprintf("%s as a template stored in a local file", s),
336+
c.TemplateClassificationErrors[s] = ArgumentClassificationError{
337+
Key: "is not a template stored in a local file",
308338
Value: err,
309-
})
339+
}
310340
}
311341
return false
312342
}
313343

314344
// AddArguments converts command line arguments into the appropriate bucket based on what they look like
315345
func (c *AppConfig) AddArguments(args []string) []string {
316346
unknown := []string{}
317-
c.ArgumentClassificationErrors = []ArgumentClassificationError{}
318347
for _, s := range args {
319348
if len(s) == 0 {
320349
continue
321350
}
322351

323352
switch {
324353
case c.tryToAddEnvironmentArguments(s):
354+
c.ClassificationWinners[s] = ArgumentClassificationWinner{Name: s, Suffix: "an environment value"}
325355
case c.tryToAddSourceArguments(s):
326-
case c.tryToAddComponentArguments(s):
356+
c.ClassificationWinners[s] = ArgumentClassificationWinner{Name: s, Suffix: "a source repository"}
357+
delete(c.EnvironmentClassificationErrors, s)
327358
case c.tryToAddTemplateArguments(s):
359+
c.ClassificationWinners[s] = ArgumentClassificationWinner{Name: s, Suffix: "a template"}
360+
delete(c.EnvironmentClassificationErrors, s)
361+
delete(c.SourceClassificationErrors, s)
362+
case c.tryToAddComponentArguments(s):
363+
// NOTE, component argument classification currently is the most lenient, so we save it for the end
364+
c.ClassificationWinners[s] = ArgumentClassificationWinner{Name: s, Suffix: "an image, image~source, or loaded template reference", IncludeGitErrors: true}
365+
delete(c.EnvironmentClassificationErrors, s)
366+
delete(c.TemplateClassificationErrors, s)
367+
// we are going to save the source errors in case this really was a source repo in the end
328368
default:
329369
glog.V(2).Infof("treating %s as unknown\n", s)
330370
unknown = append(unknown, s)

‎pkg/oc/generate/cmd/newapp_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ func TestBuildTemplates(t *testing.T) {
186186
for n, c := range tests {
187187
appCfg := AppConfig{}
188188
appCfg.Out = &bytes.Buffer{}
189+
appCfg.EnvironmentClassificationErrors = map[string]ArgumentClassificationError{}
190+
appCfg.SourceClassificationErrors = map[string]ArgumentClassificationError{}
191+
appCfg.TemplateClassificationErrors = map[string]ArgumentClassificationError{}
192+
appCfg.ComponentClassificationErrors = map[string]ArgumentClassificationError{}
193+
appCfg.ClassificationWinners = map[string]ArgumentClassificationWinner{}
189194

190195
// the previous fake was broken and didn't 404 properly. this test is relying on that
191196
templateFake := templatefakeclient.NewSimpleClientset()

‎test/cmd/newapp.sh

+22-11
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,25 @@ os::cmd::expect_success 'oc delete is myruby'
5353
os::cmd::expect_failure_and_text 'oc new-app https://github.com/openshift/nodejs-ex --strategy=docker' 'No Dockerfile was found'
5454

5555
# repo related error message validation
56-
os::cmd::expect_failure_and_text 'oc new-app mysql-persisten mysql' 'mysql-persisten as a local directory'
57-
os::cmd::expect_failure_and_text 'oc new-app mysql-persisten mysql' 'mysql as a local directory'
58-
os::cmd::expect_failure_and_text 'oc new-app --strategy=docker https://192.30.253.113/openshift/ruby-hello-world.git' 'as a Git repository URL: '
59-
os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'as a Git repository URL: '
60-
os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'as a Git repository URL: '
61-
os::cmd::expect_failure_and_text 'oc new-build --strategy=docker https://192.30.253.113/openshift/ruby-hello-world.git' 'as a Git repository URL: '
62-
os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'as a Git repository URL: '
63-
os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'as a Git repository URL: '
56+
os::cmd::expect_success 'oc create -f examples/db-templates/mysql-persistent-template.json'
57+
os::cmd::expect_failure_and_text 'oc new-app mysql-persisten mysql' 'only a partial match was found for'
58+
os::cmd::expect_success 'oc delete template/mysql-persistent'
59+
os::cmd::expect_failure_and_text 'oc new-app --strategy=docker https://192.30.253.113/openshift/ruby-hello-world.git' 'none of the arguments provided could be classified as a source code location'
60+
os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'unable to load template file'
61+
os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'unable to locate any'
62+
os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'was classified as an image, image~source, or loaded template reference.'
63+
os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'unable to load template file'
64+
os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'unable to locate any'
65+
os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'was classified as an image, image~source, or loaded template reference.'
66+
os::cmd::expect_failure_and_text 'oc new-build --strategy=docker https://192.30.253.113/openshift/ruby-hello-world.git' 'none of the arguments provided could be classified as a source code location'
67+
os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'unable to load template file'
68+
os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'unable to locate any'
69+
os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'was classified as an image, image~source, or loaded template reference.'
70+
os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'unable to load template file'
71+
os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'unable to locate any'
72+
os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'was classified as an image, image~source, or loaded template reference.'
73+
os::cmd::expect_failure_and_text 'oc new-build --name imagesourcetest python~https://github.com/openshift-katacoda/blog-django-py --source-image xxx --source-image-path=yyy --dry-run' 'unable to locate any '
74+
os::cmd::expect_failure_and_text 'oc new-app ~java' 'you must specify a image name'
6475

6576
# setting source secret via the --source-secret flag
6677
os::cmd::expect_success_and_text 'oc new-app https://github.com/openshift/cakephp-ex --source-secret=mysecret -o yaml' 'name: mysecret'
@@ -350,8 +361,8 @@ os::cmd::expect_success_and_text 'oc new-app --dry-run --docker-image=mysql' 'Th
350361
os::cmd::expect_success_and_text 'oc new-app --dry-run --docker-image=mysql' "WARNING: Image \"mysql\" runs as the 'root' user"
351362

352363
# verify multiple errors are displayed together, a nested error is returned, and that the usage message is displayed
353-
os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: no match for "__template_fail"'
354-
os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: no match for "__templatefile_fail"'
364+
os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: unable to locate any'
365+
os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'with name "__templatefile_fail"'
355366
os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: unable to find the specified template file'
356367
os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' "The 'oc new-app' command will match arguments"
357368

@@ -452,7 +463,7 @@ os::cmd::expect_success_and_text 'oc new-app -f test/testdata/circular.yaml' 'sh
452463
os::cmd::expect_success_and_not_text 'oc new-app -f test/testdata/bc-from-imagestreamimage.json --dry-run' 'Unable to follow reference type'
453464

454465
# do not allow use of non-existent image (should fail)
455-
os::cmd::expect_failure_and_text 'oc new-app openshift/bogusimage https://github.com/openshift/ruby-hello-world.git -o yaml' "no match for"
466+
os::cmd::expect_failure_and_text 'oc new-app openshift/bogusimage https://github.com/openshift/ruby-hello-world.git -o yaml' "unable to locate any"
456467
# allow use of non-existent image (should succeed)
457468
os::cmd::expect_success 'oc new-app openshift/bogusimage https://github.com/openshift/ruby-hello-world.git -o yaml --allow-missing-images'
458469

‎test/integration/newapp_test.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ func TestNewAppAddArguments(t *testing.T) {
119119

120120
for n, c := range tests {
121121
a := &cmd.AppConfig{}
122+
a.EnvironmentClassificationErrors = map[string]cmd.ArgumentClassificationError{}
123+
a.SourceClassificationErrors = map[string]cmd.ArgumentClassificationError{}
124+
a.TemplateClassificationErrors = map[string]cmd.ArgumentClassificationError{}
125+
a.ComponentClassificationErrors = map[string]cmd.ArgumentClassificationError{}
126+
a.ClassificationWinners = map[string]cmd.ArgumentClassificationWinner{}
122127
unknown := a.AddArguments(c.args)
123128
if !reflect.DeepEqual(a.Environment, c.env) {
124129
t.Errorf("%s: Different env variables. Expected: %v, Actual: %v", n, c.env, a.Environment)
@@ -154,7 +159,7 @@ func TestNewAppResolve(t *testing.T) {
154159
},
155160
},
156161
})},
157-
expectedErr: `no match for "mysql:invalid`,
162+
expectedErr: `unable to locate any`,
158163
},
159164
{
160165
name: "Successful mysql builder",
@@ -279,6 +284,10 @@ type ExactMatchDockerSearcher struct {
279284
Errs []error
280285
}
281286

287+
func (r *ExactMatchDockerSearcher) Type() string {
288+
return ""
289+
}
290+
282291
// Search always returns a match for every term passed in
283292
func (r *ExactMatchDockerSearcher) Search(precise bool, terms ...string) (app.ComponentMatches, []error) {
284293
matches := app.ComponentMatches{}
@@ -301,6 +310,10 @@ type ExactMatchDirectTagDockerSearcher struct {
301310
Errs []error
302311
}
303312

313+
func (r *ExactMatchDirectTagDockerSearcher) Type() string {
314+
return ""
315+
}
316+
304317
func (r *ExactMatchDirectTagDockerSearcher) Search(precise bool, terms ...string) (app.ComponentMatches, []error) {
305318
matches := app.ComponentMatches{}
306319
for _, value := range terms {

0 commit comments

Comments
 (0)
Please sign in to comment.