diff --git a/pkg/oc/cli/cmd/newapp.go b/pkg/oc/cli/cmd/newapp.go index 17b4b6c2579f..408af12fd68d 100644 --- a/pkg/oc/cli/cmd/newapp.go +++ b/pkg/oc/cli/cmd/newapp.go @@ -605,7 +605,20 @@ func CompleteAppConfig(config *newcmd.AppConfig, f *clientcmd.Factory, c *cobra. unknown := config.AddArguments(args) if len(unknown) != 0 { - return kcmdutil.UsageErrorf(c, "Did not recognize the following arguments: %v", unknown) + buf := &bytes.Buffer{} + fmt.Fprintf(buf, "Did not recognize the following arguments: %v\n\n", unknown) + for argName, classErrs := range config.ComponentInputClassificationErrors { + fmt.Fprintf(buf, "%s:\n", argName) + for _, classErr := range classErrs { + if classErr.Value != nil { + fmt.Fprintf(buf, fmt.Sprintf("%s: %v\n", classErr.Key, classErr.Value)) + } else { + fmt.Fprintf(buf, fmt.Sprintf("%s\n", classErr.Key)) + } + } + fmt.Fprintln(buf) + } + return kcmdutil.UsageErrorf(c, heredoc.Docf(buf.String())) } if config.AllowMissingImages && config.AsSearch { @@ -701,7 +714,7 @@ func retryBuildConfig(info *resource.Info, err error) runtime.Object { return nil } -func handleError(err error, baseName, commandName, commandPath string, config *newcmd.AppConfig, transformError func(err error, baseName, commandName, commandPath string, groups errorGroups)) error { +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 { if err == nil { return nil } @@ -711,23 +724,19 @@ func handleError(err error, baseName, commandName, commandPath string, config *n } groups := errorGroups{} for _, err := range errs { - transformError(err, baseName, commandName, commandPath, groups) + transformError(err, baseName, commandName, commandPath, groups, config) } buf := &bytes.Buffer{} - if len(config.ArgumentClassificationErrors) > 0 { - fmt.Fprintf(buf, "Errors occurred while determining argument types:\n") - for _, classErr := range config.ArgumentClassificationErrors { - fmt.Fprintf(buf, fmt.Sprintf("\n%s: %v\n", classErr.Key, classErr.Value)) - } - fmt.Fprint(buf, "\n") - // this print serves as a header for the printing of the errorGroups, but - // only print it if we precede with classification errors, to help distinguish - // between the two - fmt.Fprintln(buf, "Errors occurred during resource creation:") - } for _, group := range groups { fmt.Fprint(buf, kcmdutil.MultipleErrors("error: ", group.errs)) + if len(group.classification) > 0 { + fmt.Fprintln(buf) + } + fmt.Fprintf(buf, group.classification) if len(group.suggestion) > 0 { + if len(group.classification) > 0 { + fmt.Fprintln(buf) + } fmt.Fprintln(buf) } fmt.Fprint(buf, group.suggestion) @@ -736,20 +745,22 @@ func handleError(err error, baseName, commandName, commandPath string, config *n } type errorGroup struct { - errs []error - suggestion string + errs []error + suggestion string + classification string } type errorGroups map[string]errorGroup -func (g errorGroups) Add(group string, suggestion string, err error, errs ...error) { +func (g errorGroups) Add(group string, suggestion string, classification string, err error, errs ...error) { all := g[group] all.errs = append(all.errs, errs...) all.errs = append(all.errs, err) all.suggestion = suggestion + all.classification = classification g[group] = all } -func transformRunError(err error, baseName, commandName, commandPath string, groups errorGroups) { +func transformRunError(err error, baseName, commandName, commandPath string, groups errorGroups, config *newcmd.AppConfig) { switch t := err.(type) { case newcmd.ErrRequiresExplicitAccess: if t.Input.Token != nil && t.Input.Token.ServiceAccount { @@ -762,6 +773,7 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro You can see more information about the image by adding the --dry-run flag. If you trust the provided image, include the flag --grant-install-rights.`, ), + "", fmt.Errorf("installing %q requires an 'installer' service account with project editor access", t.Match.Value), ) } else { @@ -774,11 +786,13 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro You can see more information about the image by adding the --dry-run flag. If you trust the provided image, include the flag --grant-install-rights.`, ), + "", fmt.Errorf("installing %q requires that you grant the image access to run with your credentials", t.Match.Value), ) } return case newapp.ErrNoMatch: + classification, _ := config.ComponentInputClassificationWinners[t.Value] groups.Add( "no-matches", heredoc.Docf(` @@ -794,11 +808,13 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro See '%[1]s -h' for examples.`, commandPath, ), + heredoc.Docf(classification), t, t.Errs..., ) return case newapp.ErrMultipleMatches: + classification, _ := config.ComponentInputClassificationWinners[t.Value] buf := &bytes.Buffer{} for i, match := range t.Matches { @@ -812,6 +828,7 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro %[2]sTo view a full list of matches, use '%[3]s %[4]s -S %[1]s'`, t.Value, buf.String(), baseName, commandName, ), + classification, t, t.Errs..., ) @@ -830,11 +847,13 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro %[2]s`, t.Value, buf.String(), ), + classification, t, t.Errs..., ) return case newapp.ErrPartialMatch: + classification, _ := config.ComponentInputClassificationWinners[t.Value] buf := &bytes.Buffer{} fmt.Fprintf(buf, "* %s\n", t.Match.Description) fmt.Fprintf(buf, " Use %[1]s to specify this image or template\n\n", t.Match.Argument) @@ -846,11 +865,13 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro %[2]s`, t.Value, buf.String(), ), + classification, t, t.Errs..., ) return case newapp.ErrNoTagsFound: + classification, _ := config.ComponentInputClassificationWinners[t.Value] buf := &bytes.Buffer{} fmt.Fprintf(buf, " Use --allow-missing-imagestream-tags to use this image stream\n\n") groups.Add( @@ -860,6 +881,7 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro %[2]s`, t.Match.Name, buf.String(), ), + classification, t, t.Errs..., ) @@ -868,13 +890,14 @@ func transformRunError(err error, baseName, commandName, commandPath string, gro switch err { case errNoTokenAvailable: // TODO: improve by allowing token generation - groups.Add("", "", fmt.Errorf("to install components you must be logged in with an OAuth token (instead of only a certificate)")) + groups.Add("", "", "", fmt.Errorf("to install components you must be logged in with an OAuth token (instead of only a certificate)")) case newcmd.ErrNoInputs: // TODO: suggest things to the user - groups.Add("", "", usageError(commandPath, newAppNoInput, baseName, commandName)) + groups.Add("", "", "", usageError(commandPath, newAppNoInput, baseName, commandName)) default: - groups.Add("", "", err) + groups.Add("", "", "", err) } + return } func usageError(commandPath, format string, args ...interface{}) error { diff --git a/pkg/oc/cli/cmd/newbuild.go b/pkg/oc/cli/cmd/newbuild.go index e257c1e40be9..153ea7e89d8b 100644 --- a/pkg/oc/cli/cmd/newbuild.go +++ b/pkg/oc/cli/cmd/newbuild.go @@ -212,9 +212,10 @@ func (o *NewBuildOptions) RunNewBuild() error { return nil } -func transformBuildError(err error, baseName, commandName, commandPath string, groups errorGroups) { +func transformBuildError(err error, baseName, commandName, commandPath string, groups errorGroups, config *newcmd.AppConfig) { switch t := err.(type) { case newapp.ErrNoMatch: + classification, _ := config.ComponentInputClassificationWinners[t.Value] groups.Add( "no-matches", heredoc.Docf(` @@ -229,6 +230,7 @@ func transformBuildError(err error, baseName, commandName, commandPath string, g See '%[1]s -h' for examples.`, commandPath, ), + classification, t, t.Errs..., ) @@ -236,8 +238,9 @@ func transformBuildError(err error, baseName, commandName, commandPath string, g } switch err { case newcmd.ErrNoInputs: - groups.Add("", "", usageError(commandPath, newBuildNoInput, baseName, commandName)) + groups.Add("", "", "", usageError(commandPath, newBuildNoInput, baseName, commandName)) return } - transformRunError(err, baseName, commandName, commandPath, groups) + transformRunError(err, baseName, commandName, commandPath, groups, config) + return } diff --git a/pkg/oc/cli/cmd/newbuild_test.go b/pkg/oc/cli/cmd/newbuild_test.go index 429ee997571c..fd56438f8ddd 100644 --- a/pkg/oc/cli/cmd/newbuild_test.go +++ b/pkg/oc/cli/cmd/newbuild_test.go @@ -108,6 +108,10 @@ type MockSearcher struct { OnSearch func(precise bool, terms ...string) (app.ComponentMatches, []error) } +func (m MockSearcher) Type() string { + return "" +} + // Search mocks a search. func (m MockSearcher) Search(precise bool, terms ...string) (app.ComponentMatches, []error) { return m.OnSearch(precise, terms...) diff --git a/pkg/oc/generate/app/componentresolvers.go b/pkg/oc/generate/app/componentresolvers.go index 5d774641a580..4e89058d409d 100644 --- a/pkg/oc/generate/app/componentresolvers.go +++ b/pkg/oc/generate/app/componentresolvers.go @@ -2,6 +2,7 @@ package app import ( "sort" + "strings" "github.com/golang/glog" @@ -24,6 +25,7 @@ type Resolver interface { // matches type Searcher interface { Search(precise bool, terms ...string) (ComponentMatches, []error) + Type() string } // WeightedResolver is a resolver identified as exact or not, depending on its weight @@ -42,6 +44,7 @@ type PerfectMatchWeightedResolver []WeightedResolver // Resolve resolves the provided input and returns only exact matches func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, error) { var errs []error + types := []string{} candidates := ScoredComponentMatches{} var group MultiSimpleSearcher var groupWeight float32 = 0.0 @@ -59,6 +62,7 @@ func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, er glog.V(5).Infof("Error from resolver: %v\n", err) errs = append(errs, err...) } + types = append(types, group.Type()) sort.Sort(ScoredComponentMatches(matches)) 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 switch len(candidates) { case 0: - return nil, ErrNoMatch{Value: value, Errs: errs} + return nil, ErrNoMatch{Value: value, Errs: errs, Type: strings.Join(types, ", ")} case 1: if candidates[0].Score != 0.0 { if candidates[0].NoTagsFound { @@ -108,7 +112,7 @@ type FirstMatchResolver struct { func (r FirstMatchResolver) Resolve(value string) (*ComponentMatch, error) { matches, err := r.Searcher.Search(true, value) if len(matches) == 0 { - return nil, ErrNoMatch{Value: value, Errs: err} + return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()} } return matches[0], errors.NewAggregate(err) } @@ -125,7 +129,7 @@ type HighestScoreResolver struct { func (r HighestScoreResolver) Resolve(value string) (*ComponentMatch, error) { matches, err := r.Searcher.Search(true, value) if len(matches) == 0 { - return nil, ErrNoMatch{Value: value, Errs: err} + return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()} } sort.Sort(ScoredComponentMatches(matches)) return matches[0], errors.NewAggregate(err) @@ -146,7 +150,7 @@ func (r HighestUniqueScoreResolver) Resolve(value string) (*ComponentMatch, erro sort.Sort(ScoredComponentMatches(matches)) switch len(matches) { case 0: - return nil, ErrNoMatch{Value: value, Errs: err} + return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()} case 1: return matches[0], errors.NewAggregate(err) default: @@ -184,7 +188,7 @@ func (r UniqueExactOrInexactMatchResolver) Resolve(value string) (*ComponentMatc inexact := matches.Inexact() switch len(inexact) { case 0: - return nil, ErrNoMatch{Value: value, Errs: err} + return nil, ErrNoMatch{Value: value, Errs: err, Type: r.Searcher.Type()} case 1: return inexact[0], errors.NewAggregate(err) default: @@ -213,6 +217,14 @@ func (r PipelineResolver) Resolve(value string) (*ComponentMatch, error) { // MultiSimpleSearcher is a set of searchers type MultiSimpleSearcher []Searcher +func (s MultiSimpleSearcher) Type() string { + t := []string{} + for _, searcher := range s { + t = append(t, searcher.Type()) + } + return strings.Join(t, ", ") +} + // Search searches using all searchers it holds func (s MultiSimpleSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { var errs []error @@ -239,6 +251,14 @@ type WeightedSearcher struct { // priority in search results type MultiWeightedSearcher []WeightedSearcher +func (s MultiWeightedSearcher) Type() string { + t := []string{} + for _, searcher := range s { + t = append(t, searcher.Type()) + } + return strings.Join(t, ", ") +} + // Search searches using all searchers it holds and score according to searcher height func (s MultiWeightedSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { componentMatches := ComponentMatches{} diff --git a/pkg/oc/generate/app/componentresolvers_test.go b/pkg/oc/generate/app/componentresolvers_test.go index d0665f24f220..df1ff91df9ff 100644 --- a/pkg/oc/generate/app/componentresolvers_test.go +++ b/pkg/oc/generate/app/componentresolvers_test.go @@ -9,6 +9,10 @@ type mockSearcher struct { numResults int } +func (m mockSearcher) Type() string { + return "" +} + func (m mockSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { results := ComponentMatches{} for i := 0; i < m.numResults; i++ { diff --git a/pkg/oc/generate/app/dockerimagelookup.go b/pkg/oc/generate/app/dockerimagelookup.go index 2ac430012603..b3da0ab8e910 100644 --- a/pkg/oc/generate/app/dockerimagelookup.go +++ b/pkg/oc/generate/app/dockerimagelookup.go @@ -41,6 +41,10 @@ type DockerClientSearcher struct { AllowMissingImages bool } +func (r DockerClientSearcher) Type() string { + return "local docker images" +} + // Search searches all images in local docker server for images that match terms func (r DockerClientSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { componentMatches := ComponentMatches{} @@ -164,6 +168,10 @@ func (r DockerClientSearcher) Search(precise bool, terms ...string) (ComponentMa type MissingImageSearcher struct { } +func (r MissingImageSearcher) Type() string { + return "images not found in docker registry nor locally" +} + // Search always returns an exact match for the search terms. func (r MissingImageSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { componentMatches := ComponentMatches{} @@ -184,6 +192,10 @@ type ImageImportSearcher struct { Fallback Searcher } +func (s ImageImportSearcher) Type() string { + return "images via the image stream import API" +} + // Search invokes the new ImageStreamImport API to have the server look up Docker images for the user, // using secrets stored on the server. func (s ImageImportSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { @@ -267,6 +279,10 @@ type DockerRegistrySearcher struct { AllowInsecure bool } +func (r DockerRegistrySearcher) Type() string { + return "images in the docker registry" +} + // Search searches in the Docker registry for images that match terms func (r DockerRegistrySearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { componentMatches := ComponentMatches{} diff --git a/pkg/oc/generate/app/dockerimagelookup_test.go b/pkg/oc/generate/app/dockerimagelookup_test.go index 19032a501f3d..d816cfa71324 100644 --- a/pkg/oc/generate/app/dockerimagelookup_test.go +++ b/pkg/oc/generate/app/dockerimagelookup_test.go @@ -12,6 +12,10 @@ type fakeRegistrySearcher struct { errs []error } +func (f fakeRegistrySearcher) Type() string { + return "" +} + func (f fakeRegistrySearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { return f.matches, f.errs } diff --git a/pkg/oc/generate/app/errors.go b/pkg/oc/generate/app/errors.go index 1e38fd825a56..6432ff3c6cbd 100644 --- a/pkg/oc/generate/app/errors.go +++ b/pkg/oc/generate/app/errors.go @@ -11,15 +11,16 @@ import ( // given component. type ErrNoMatch struct { Value string + Type string Qualifier string Errs []error } func (e ErrNoMatch) Error() string { if len(e.Qualifier) != 0 { - return fmt.Sprintf("no match for %q: %s", e.Value, e.Qualifier) + return fmt.Sprintf("unable to locate any %s with name %q: %s", e.Type, e.Value, e.Qualifier) } - return fmt.Sprintf("no match for %q", e.Value) + return fmt.Sprintf("unable to locate any %s with name %q", e.Type, e.Value) } // Suggestion is the usage error message returned when no match is found. diff --git a/pkg/oc/generate/app/imagestreamlookup.go b/pkg/oc/generate/app/imagestreamlookup.go index 4e010fcd215d..932df2452916 100644 --- a/pkg/oc/generate/app/imagestreamlookup.go +++ b/pkg/oc/generate/app/imagestreamlookup.go @@ -20,6 +20,10 @@ type ImageStreamSearcher struct { AllowMissingTags bool } +func (r ImageStreamSearcher) Type() string { + return "images in image streams" +} + // Search will attempt to find imagestreams with names that match the passed in value func (r ImageStreamSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { componentMatches := ComponentMatches{} @@ -362,6 +366,10 @@ func (r *ImageStreamByAnnotationSearcher) annotationMatches(stream *imageapi.Ima return matches } +func (r *ImageStreamByAnnotationSearcher) Type() string { + return "image stream images with a 'supports' annotation" +} + // Search finds image stream images using their 'supports' annotation func (r *ImageStreamByAnnotationSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { matches := ComponentMatches{} diff --git a/pkg/oc/generate/app/templatelookup.go b/pkg/oc/generate/app/templatelookup.go index 21675ef06b6c..6c68e6515778 100644 --- a/pkg/oc/generate/app/templatelookup.go +++ b/pkg/oc/generate/app/templatelookup.go @@ -25,6 +25,10 @@ type TemplateSearcher struct { StopOnExactMatch bool } +func (r TemplateSearcher) Type() string { + return "templates loaded in accessible projects" +} + // Search searches for a template and returns matches with the object representation func (r TemplateSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { matches := ComponentMatches{} @@ -104,6 +108,10 @@ type TemplateFileSearcher struct { Namespace string } +func (r *TemplateFileSearcher) Type() string { + return "template files" +} + // Search attempts to read template files and transform it into template objects func (r *TemplateFileSearcher) Search(precise bool, terms ...string) (ComponentMatches, []error) { matches := ComponentMatches{} diff --git a/pkg/oc/generate/cmd/newapp.go b/pkg/oc/generate/cmd/newapp.go index 9c89319d6de8..f2b37ea83a7c 100644 --- a/pkg/oc/generate/cmd/newapp.go +++ b/pkg/oc/generate/cmd/newapp.go @@ -132,7 +132,8 @@ type AppConfig struct { OriginNamespace string - ArgumentClassificationErrors []ArgumentClassificationError + ComponentInputClassificationErrors map[string][]ArgumentClassificationError + ComponentInputClassificationWinners map[string]string } type ArgumentClassificationError struct { @@ -180,6 +181,8 @@ func NewAppConfig() *AppConfig { JenkinsfileTester: jenkinsfile.NewTester(), }, }, + ComponentInputClassificationErrors: map[string][]ArgumentClassificationError{}, + ComponentInputClassificationWinners: map[string]string{}, } } @@ -248,6 +251,15 @@ func (c *AppConfig) tryToAddEnvironmentArguments(s string) bool { if rc { glog.V(2).Infof("treating %s as possible environment argument\n", s) c.Environment = append(c.Environment, s) + } else { + errs, ok := c.ComponentInputClassificationErrors[s] + if !ok { + errs = []ArgumentClassificationError{} + } + c.ComponentInputClassificationErrors[s] = append(errs, ArgumentClassificationError{ + Key: "is not an environment variable", + Value: nil, + }) } return rc } @@ -262,19 +274,26 @@ func (c *AppConfig) tryToAddSourceArguments(s string) bool { return true } + // will combine multiple errors into one line / string + errStr := "" if rerr != nil { - c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{ - Key: fmt.Sprintf("%s as a Git repository URL", s), - Value: rerr, - }) + errStr = fmt.Sprintf("%v", rerr) } if derr != nil { - c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{ - Key: fmt.Sprintf("%s as a local directory pointing to a Git repository", s), - Value: derr, - }) + if len(errStr) > 0 { + errStr = errStr + "; " + } + errStr = fmt.Sprintf("%s%v", errStr, derr) + } + errs, ok := c.ComponentInputClassificationErrors[s] + if !ok { + errs = []ArgumentClassificationError{} } + c.ComponentInputClassificationErrors[s] = append(errs, ArgumentClassificationError{ + Key: "is not a Git repository", + Value: fmt.Errorf(errStr), + }) return false } @@ -286,8 +305,12 @@ func (c *AppConfig) tryToAddComponentArguments(s string) bool { c.Components = append(c.Components, s) return true } - c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{ - Key: fmt.Sprintf("%s as a template loaded in an accessible project, an imagestream tag, or a docker image reference", s), + errs, ok := c.ComponentInputClassificationErrors[s] + if !ok { + errs = []ArgumentClassificationError{} + } + c.ComponentInputClassificationErrors[s] = append(errs, ArgumentClassificationError{ + Key: "is not an image reference, source repository, nor template loaded in an accessible project", Value: err, }) @@ -302,8 +325,12 @@ func (c *AppConfig) tryToAddTemplateArguments(s string) bool { return true } if err != nil { - c.ArgumentClassificationErrors = append(c.ArgumentClassificationErrors, ArgumentClassificationError{ - Key: fmt.Sprintf("%s as a template stored in a local file", s), + errs, ok := c.ComponentInputClassificationErrors[s] + if !ok { + errs = []ArgumentClassificationError{} + } + c.ComponentInputClassificationErrors[s] = append(errs, ArgumentClassificationError{ + Key: "is not a template stored in a local file", Value: err, }) } @@ -313,7 +340,7 @@ func (c *AppConfig) tryToAddTemplateArguments(s string) bool { // AddArguments converts command line arguments into the appropriate bucket based on what they look like func (c *AppConfig) AddArguments(args []string) []string { unknown := []string{} - c.ArgumentClassificationErrors = []ArgumentClassificationError{} + winnerFmt := "Argument '%s' was classified as %s" for _, s := range args { if len(s) == 0 { continue @@ -321,9 +348,14 @@ func (c *AppConfig) AddArguments(args []string) []string { switch { case c.tryToAddEnvironmentArguments(s): + c.ComponentInputClassificationWinners[s] = fmt.Sprintf(winnerFmt, s, "an environment value.") case c.tryToAddSourceArguments(s): - case c.tryToAddComponentArguments(s): + c.ComponentInputClassificationWinners[s] = fmt.Sprintf(winnerFmt, s, "a source repository.") case c.tryToAddTemplateArguments(s): + c.ComponentInputClassificationWinners[s] = fmt.Sprintf(winnerFmt, s, "a template.") + case c.tryToAddComponentArguments(s): + // NOTE, component argument classification currently is the most lenient, so we save it for the end + c.ComponentInputClassificationWinners[s] = fmt.Sprintf(winnerFmt, s, "an image reference and/or source repository.") default: glog.V(2).Infof("treating %s as unknown\n", s) unknown = append(unknown, s) diff --git a/pkg/oc/generate/cmd/newapp_test.go b/pkg/oc/generate/cmd/newapp_test.go index a8395725db57..b9c50eb44aeb 100644 --- a/pkg/oc/generate/cmd/newapp_test.go +++ b/pkg/oc/generate/cmd/newapp_test.go @@ -186,6 +186,8 @@ func TestBuildTemplates(t *testing.T) { for n, c := range tests { appCfg := AppConfig{} appCfg.Out = &bytes.Buffer{} + appCfg.ComponentInputClassificationErrors = map[string][]ArgumentClassificationError{} + appCfg.ComponentInputClassificationWinners = map[string]string{} // the previous fake was broken and didn't 404 properly. this test is relying on that templateFake := templatefakeclient.NewSimpleClientset() diff --git a/test/cmd/newapp.sh b/test/cmd/newapp.sh index d5958eb3c828..2d80c64075cd 100755 --- a/test/cmd/newapp.sh +++ b/test/cmd/newapp.sh @@ -53,14 +53,26 @@ os::cmd::expect_success 'oc delete is myruby' os::cmd::expect_failure_and_text 'oc new-app https://github.com/openshift/nodejs-ex --strategy=docker' 'No Dockerfile was found' # repo related error message validation -os::cmd::expect_failure_and_text 'oc new-app mysql-persisten mysql' 'mysql-persisten as a local directory' -os::cmd::expect_failure_and_text 'oc new-app mysql-persisten mysql' 'mysql as a local directory' -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: ' -os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'as a Git repository URL: ' -os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'as a Git repository URL: ' -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: ' -os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'as a Git repository URL: ' -os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'as a Git repository URL: ' +# oc new-app mysql-persisten mysql reacts differently when run after oc cluster up; with cluster up, it +# finds the mysql-persistent template in the openshift namespace and cites a partial match, but in this test +# that namespace/template combo does not exist, and so falls back to the generic not match / unable to locate resource +#os::cmd::expect_failure_and_text 'oc new-app mysql-persisten mysql' 'only a partial match was found for' +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' +os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'unable to load template file' +os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'unable to locate any' +os::cmd::expect_failure_and_text 'oc new-app https://www.google.com/openshift/nodejs-e' 'was classified as an image reference and/or source repository.' +os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'unable to load template file' +os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'unable to locate any' +os::cmd::expect_failure_and_text 'oc new-app https://examplegit.com/openshift/nodejs-e' 'was classified as an image reference and/or source repository.' +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' +os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'unable to load template file' +os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'unable to locate any' +os::cmd::expect_failure_and_text 'oc new-build https://www.google.com/openshift/nodejs-e' 'was classified as an image reference and/or source repository.' +os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'unable to load template file' +os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'unable to locate any' +os::cmd::expect_failure_and_text 'oc new-build https://examplegit.com/openshift/nodejs-e' 'was classified as an image reference and/or source repository.' +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 ' +os::cmd::expect_failure_and_text 'oc new-app ~java' 'you must specify a image name' # setting source secret via the --source-secret flag os::cmd::expect_success_and_text 'oc new-app https://github.com/openshift/cakephp-ex --source-secret=mysecret -o yaml' 'name: mysecret' @@ -346,8 +358,8 @@ os::cmd::expect_success_and_text 'oc new-app --dry-run --docker-image=mysql' 'Th os::cmd::expect_success_and_text 'oc new-app --dry-run --docker-image=mysql' "WARNING: Image \"mysql\" runs as the 'root' user" # verify multiple errors are displayed together, a nested error is returned, and that the usage message is displayed -os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: no match for "__template_fail"' -os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: no match for "__templatefile_fail"' +os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: unable to locate any' +os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'with name "__templatefile_fail"' os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' 'error: unable to find the specified template file' os::cmd::expect_failure_and_text 'oc new-app --dry-run __template_fail __templatefile_fail' "The 'oc new-app' command will match arguments" @@ -448,7 +460,7 @@ os::cmd::expect_success_and_text 'oc new-app -f test/testdata/circular.yaml' 'sh os::cmd::expect_success_and_not_text 'oc new-app -f test/testdata/bc-from-imagestreamimage.json --dry-run' 'Unable to follow reference type' # do not allow use of non-existent image (should fail) -os::cmd::expect_failure_and_text 'oc new-app openshift/bogusimage https://github.com/openshift/ruby-hello-world.git -o yaml' "no match for" +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" # allow use of non-existent image (should succeed) os::cmd::expect_success 'oc new-app openshift/bogusimage https://github.com/openshift/ruby-hello-world.git -o yaml --allow-missing-images' diff --git a/test/integration/newapp_test.go b/test/integration/newapp_test.go index bca5c1209b61..74e92abcb864 100644 --- a/test/integration/newapp_test.go +++ b/test/integration/newapp_test.go @@ -119,6 +119,8 @@ func TestNewAppAddArguments(t *testing.T) { for n, c := range tests { a := &cmd.AppConfig{} + a.ComponentInputClassificationErrors = map[string][]cmd.ArgumentClassificationError{} + a.ComponentInputClassificationWinners = map[string]string{} unknown := a.AddArguments(c.args) if !reflect.DeepEqual(a.Environment, c.env) { t.Errorf("%s: Different env variables. Expected: %v, Actual: %v", n, c.env, a.Environment) @@ -154,7 +156,7 @@ func TestNewAppResolve(t *testing.T) { }, }, })}, - expectedErr: `no match for "mysql:invalid`, + expectedErr: `unable to locate any`, }, { name: "Successful mysql builder", @@ -279,6 +281,10 @@ type ExactMatchDockerSearcher struct { Errs []error } +func (r *ExactMatchDockerSearcher) Type() string { + return "" +} + // Search always returns a match for every term passed in func (r *ExactMatchDockerSearcher) Search(precise bool, terms ...string) (app.ComponentMatches, []error) { matches := app.ComponentMatches{} @@ -301,6 +307,10 @@ type ExactMatchDirectTagDockerSearcher struct { Errs []error } +func (r *ExactMatchDirectTagDockerSearcher) Type() string { + return "" +} + func (r *ExactMatchDirectTagDockerSearcher) Search(precise bool, terms ...string) (app.ComponentMatches, []error) { matches := app.ComponentMatches{} for _, value := range terms {