Skip to content

Commit

Permalink
helm search: New CLI Flag --fail-on-no-result
Browse files Browse the repository at this point in the history
Add new CLI flag --fail-on-no-result for failing the helm
search when there is no result found. This works with:
1. helm search repo
2. helm search hub

Signed-off-by: Bhargav Ravuri <bhargav.ravuri@infracloud.io>
  • Loading branch information
Bhargav-InfraCloud committed Mar 6, 2023
1 parent e007c90 commit b9cece6
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 17 deletions.
25 changes: 19 additions & 6 deletions cmd/helm/search_hub.go
Expand Up @@ -54,6 +54,7 @@ type searchHubOptions struct {
maxColWidth uint
outputFormat output.Format
listRepoURL bool
failOnNoResult bool
}

func newSearchHubCmd(out io.Writer) *cobra.Command {
Expand All @@ -72,6 +73,7 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
f.BoolVar(&o.listRepoURL, "list-repo-url", false, "print charts repository URL")
f.BoolVar(&o.failOnNoResult, "fail-on-no-result", false, "search fails if no results are found")

bindOutputFlag(cmd, &o.outputFormat)

Expand All @@ -91,7 +93,7 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
return fmt.Errorf("unable to perform search against %q", o.searchEndpoint)
}

return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL))
return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL, o.failOnNoResult))
}

type hubChartRepo struct {
Expand All @@ -108,12 +110,13 @@ type hubChartElement struct {
}

type hubSearchWriter struct {
elements []hubChartElement
columnWidth uint
listRepoURL bool
elements []hubChartElement
columnWidth uint
listRepoURL bool
failOnNoResult bool
}

func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL bool) *hubSearchWriter {
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL, failOnNoResult bool) *hubSearchWriter {
var elements []hubChartElement
for _, r := range results {
// Backwards compatibility for Monocular
Expand All @@ -126,11 +129,16 @@ func newHubSearchWriter(results []monocular.SearchResult, endpoint string, colum

elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description, hubChartRepo{URL: r.Attributes.Repo.URL, Name: r.Attributes.Repo.Name}})
}
return &hubSearchWriter{elements, columnWidth, listRepoURL}
return &hubSearchWriter{elements, columnWidth, listRepoURL, failOnNoResult}
}

func (h *hubSearchWriter) WriteTable(out io.Writer) error {
if len(h.elements) == 0 {
// Fail if no results found and --fail-on-no-result is enabled
if h.failOnNoResult {
return fmt.Errorf("no results found")
}

_, err := out.Write([]byte("No results found\n"))
if err != nil {
return fmt.Errorf("unable to write results: %s", err)
Expand Down Expand Up @@ -165,6 +173,11 @@ func (h *hubSearchWriter) WriteYAML(out io.Writer) error {
}

func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) error {
// Fail if no results found and --fail-on-no-result is enabled
if len(h.elements) == 0 && h.failOnNoResult {
return fmt.Errorf("no results found")
}

// Initialize the array so no results returns an empty array instead of null
chartList := make([]hubChartElement, 0, len(h.elements))

Expand Down
95 changes: 95 additions & 0 deletions cmd/helm/search_hub_test.go
Expand Up @@ -90,3 +90,98 @@ func TestSearchHubOutputCompletion(t *testing.T) {
func TestSearchHubFileCompletion(t *testing.T) {
checkFileCompletion(t, "search hub", true) // File completion may be useful when inputting a keyword
}

func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) {
var (
searchResult = `{"data":[]}`
noResultFoundErr = "Error: no results found\n"
noResultFoundWarn = "No results found\n"
noResultFoundWarnInList = "[]\n"
)

type testCase struct {
name string
cmd string
response string
expected string
wantErr bool
}

var tests = []testCase{
{
name: "Search hub with no results in response",
cmd: `search hub maria`,
response: searchResult,
expected: noResultFoundWarn,
wantErr: false,
},
{
name: "Search hub with no results in response and output JSON",
cmd: `search hub maria --output json`,
response: searchResult,
expected: noResultFoundWarnInList,
wantErr: false,
},
{
name: "Search hub with no results in response and output YAML",
cmd: `search hub maria --output yaml`,
response: searchResult,
expected: noResultFoundWarnInList,
wantErr: false,
},
{
name: "Search hub with no results in response and --fail-on-no-result enabled, expected failure",
cmd: `search hub maria --fail-on-no-result`,
response: searchResult,
expected: noResultFoundErr,
wantErr: true,
},
{
name: "Search hub with no results in response, output JSON and --fail-on-no-result enabled, expected failure",
cmd: `search hub maria --fail-on-no-result --output json`,
response: searchResult,
expected: noResultFoundErr,
wantErr: true,
},
{
name: "Search hub with no results in response, output YAML and --fail-on-no-result enabled, expected failure",
cmd: `search hub maria --fail-on-no-result --output yaml`,
response: searchResult,
expected: noResultFoundErr,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup a mock search service
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, tt.response)
}))
defer ts.Close()

// Add mock server URL to command
tt.cmd += " --endpoint " + ts.URL

storage := storageFixture()

_, out, err := executeActionCommandC(storage, tt.cmd)
if tt.wantErr {
if err == nil {
t.Errorf("expected error due to no record in response, got nil")
}
} else {
if err != nil {
t.Errorf("unexpected error, got %q", err)
}
}

if out != tt.expected {
t.Errorf("expected and actual output did not match\n"+
"expected: %q\n"+
"actual : %q",
tt.expected, out)
}
})
}
}
36 changes: 25 additions & 11 deletions cmd/helm/search_repo.go
Expand Up @@ -64,14 +64,15 @@ Repositories are managed with 'helm repo' commands.
const searchMaxScore = 25

type searchRepoOptions struct {
versions bool
regexp bool
devel bool
version string
maxColWidth uint
repoFile string
repoCacheDir string
outputFormat output.Format
versions bool
regexp bool
devel bool
version string
maxColWidth uint
repoFile string
repoCacheDir string
outputFormat output.Format
failOnNoResult bool
}

func newSearchRepoCmd(out io.Writer) *cobra.Command {
Expand All @@ -94,6 +95,8 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command {
f.BoolVar(&o.devel, "devel", false, "use development versions (alpha, beta, and release candidate releases), too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.StringVar(&o.version, "version", "", "search using semantic versioning constraints on repositories you have added")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
f.BoolVar(&o.failOnNoResult, "fail-on-no-result", false, "search fails if no results are found")

bindOutputFlag(cmd, &o.outputFormat)

return cmd
Expand Down Expand Up @@ -124,7 +127,7 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error {
return err
}

return o.outputFormat.Write(out, &repoSearchWriter{data, o.maxColWidth})
return o.outputFormat.Write(out, &repoSearchWriter{data, o.maxColWidth, o.failOnNoResult})
}

func (o *searchRepoOptions) setupSearchedVersion() {
Expand Down Expand Up @@ -205,12 +208,18 @@ type repoChartElement struct {
}

type repoSearchWriter struct {
results []*search.Result
columnWidth uint
results []*search.Result
columnWidth uint
failOnNoResult bool
}

func (r *repoSearchWriter) WriteTable(out io.Writer) error {
if len(r.results) == 0 {
// Fail if no results found and --fail-on-no-result is enabled
if r.failOnNoResult {
return fmt.Errorf("no results found")
}

_, err := out.Write([]byte("No results found\n"))
if err != nil {
return fmt.Errorf("unable to write results: %s", err)
Expand All @@ -235,6 +244,11 @@ func (r *repoSearchWriter) WriteYAML(out io.Writer) error {
}

func (r *repoSearchWriter) encodeByFormat(out io.Writer, format output.Format) error {
// Fail if no results found and --fail-on-no-result is enabled
if len(r.results) == 0 && r.failOnNoResult {
return fmt.Errorf("no results found")
}

// Initialize the array so no results returns an empty array instead of null
chartList := make([]repoChartElement, 0, len(r.results))

Expand Down
14 changes: 14 additions & 0 deletions cmd/helm/search_repo_test.go
Expand Up @@ -56,6 +56,20 @@ func TestSearchRepositoriesCmd(t *testing.T) {
name: "search for 'syzygy', expect no matches",
cmd: "search repo syzygy",
golden: "output/search-not-found.txt",
}, {
name: "search for 'syzygy' with --fail-on-no-result, expect failure for no results",
cmd: "search repo syzygy --fail-on-no-result",
golden: "output/search-not-found-error.txt",
wantError: true,
}, {name: "search for 'syzygy' with json output and --fail-on-no-result, expect failure for no results",
cmd: "search repo syzygy --output json --fail-on-no-result",
golden: "output/search-not-found-error.txt",
wantError: true,
}, {
name: "search for 'syzygy' with yaml output --fail-on-no-result, expect failure for no results",
cmd: "search repo syzygy --output yaml --fail-on-no-result",
golden: "output/search-not-found-error.txt",
wantError: true,
}, {
name: "search for 'alp[a-z]+', expect two matches",
cmd: "search repo alp[a-z]+ --regexp",
Expand Down
1 change: 1 addition & 0 deletions cmd/helm/testdata/output/search-not-found-error.txt
@@ -0,0 +1 @@
Error: no results found

0 comments on commit b9cece6

Please sign in to comment.