diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..4ab192c --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,30 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Build + run: go build ./... + + - name: Lint + uses: golangci/golangci-lint-action@v7 + with: + version: v2.11.3 + + - name: Test + run: go test -race -coverprofile=coverage.out ./... diff --git a/cmd/root.go b/cmd/root.go index a1ddfba..582ca20 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -170,7 +170,7 @@ func initConfig() { if err != nil { klog.Exitf("failed to download config: %s", err.Error()) } - defer os.Remove(cfgFile) + defer func() { _ = os.Remove(cfgFile) }() } // Read config @@ -206,14 +206,14 @@ func downloadConfig(cfgURL string) (string, error) { if err != nil { return "", &errors.StatusError{} } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() size, err := io.Copy(file, resp.Body) if err != nil { return "", err } - defer file.Close() + defer func() { _ = file.Close() }() tmpConfig := file.Name() klog.V(2).Infof("downloaded config file %s with size %d", tmpConfig, size) @@ -249,7 +249,7 @@ var findCmd = &cobra.Command{ kubeContext := viper.GetString("context") format := viper.GetString("format") - if !(format == output.TableFormat || format == output.JSONFormat) { + if format != output.TableFormat && format != output.JSONFormat { klog.Exitf("--format flag value is not valid. Run `nova find --help` to see flag options") } @@ -383,11 +383,17 @@ func handleHelm(kubeContext string) (*output.Output, error) { if viper.GetBool("poll-artifacthub") { ahClient, err := nova_helm.NewArtifactHubCachedPackageClient(version) + if err != nil { + return nil, fmt.Errorf("error setting up artifact hub client: %v", err) + } helmClient, err := nova_helm.NewHelmRepoHubCachedPackageClient(version) if err != nil { - return nil, fmt.Errorf("error setting up artifact hub client: %s", err) + return nil, fmt.Errorf("error setting up helm repo hub client: %v", err) } packages, err := ahClient.List() + if err != nil { + return nil, fmt.Errorf("error listing artifacthub packages: %v", err) + } helmRepos, err := helmClient.ListRepo() if err != nil { return nil, fmt.Errorf("error getting artifacthub package repos: %v", err) diff --git a/pkg/containers/images_test.go b/pkg/containers/images_test.go index c0c8168..f01f0f8 100644 --- a/pkg/containers/images_test.go +++ b/pkg/containers/images_test.go @@ -15,7 +15,6 @@ package containers import ( - "context" "encoding/json" "reflect" "testing" @@ -379,16 +378,3 @@ func TestPreReleaseRegex(t *testing.T) { } } -func setupKubeObjects(t *testing.T, c *Client) { - _, err := c.Kube.Client.CoreV1().Pods(testNamespace).Create(context.TODO(), testPodSpec, metav1.CreateOptions{}) - if err != nil { - t.Errorf("Error creating pod: %v", err) - } -} - -func teardownKubeObjects(t *testing.T, c *Client) { - err := c.Kube.Client.CoreV1().Pods(testNamespace).Delete(context.TODO(), testPodName, metav1.DeleteOptions{}) - if err != nil { - t.Errorf("Error deleting pod: %v", err) - } -} diff --git a/pkg/helm/artifacthub.go b/pkg/helm/artifacthub.go index 6e3046f..2f8fd21 100644 --- a/pkg/helm/artifacthub.go +++ b/pkg/helm/artifacthub.go @@ -180,9 +180,7 @@ func (ac *ArtifactHubPackageClient) SearchPackages(searchTerm string) ([]Artifac klog.V(5).Infof("found %d packages matching '%s'", totalCount, searchTerm) ret := make([]ArtifactHubPackageSearch, totalCount) - for i, p := range firstSet.Packages { - ret[i] = p - } + copy(ret, firstSet.Packages) if totalCount > maxArtifactHubRequestLimit { wg := sync.WaitGroup{} for i := maxArtifactHubRequestLimit; i < totalCount; i += maxArtifactHubRequestLimit { @@ -286,7 +284,7 @@ func (ac *ArtifactHubPackageClient) Search(searchTerm string, offset int) (ret A } } if resp.StatusCode == http.StatusOK { - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() err := json.NewDecoder(resp.Body).Decode(&ret) if err != nil { klog.V(3).Infof("error decoding search json with search term '%s': %s", searchTerm, err) @@ -336,7 +334,7 @@ func (ac *ArtifactHubPackageClient) getSpecific(path string) (ret ArtifactHubPac } } if resp.StatusCode == http.StatusOK { - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() err := json.NewDecoder(resp.Body).Decode(&ret.Package) if err != nil { klog.V(3).Infof("error decoding response for path %s:\n%v", path, err) diff --git a/pkg/helm/findscore_test.go b/pkg/helm/findscore_test.go index b35b87c..a52cae5 100644 --- a/pkg/helm/findscore_test.go +++ b/pkg/helm/findscore_test.go @@ -76,6 +76,7 @@ var ahubPackage = ArtifactHubHelmPackage{ var helmRelease = &release.Release{ Name: "test", Namespace: "test", + Info: &release.Info{}, Chart: &chart.Chart{ Metadata: &chart.Metadata{ Name: "test", diff --git a/pkg/helm/helm_repo_exporter.go b/pkg/helm/helm_repo_exporter.go index c223944..19d3811 100644 --- a/pkg/helm/helm_repo_exporter.go +++ b/pkg/helm/helm_repo_exporter.go @@ -35,8 +35,6 @@ const ( helmExporterAPIRoot = "https://artifacthub.io/api/v1/helm-exporter" ) -var helmRepocacheFile = "" - // ArtifactHubCachedPackageClient provides the various pieces to interact with the ArtifactHubCached API. type ArtifactHelmCachedPackageClient struct { APIRoot string diff --git a/pkg/output/file_test.go b/pkg/output/file_test.go index bef8506..412278b 100644 --- a/pkg/output/file_test.go +++ b/pkg/output/file_test.go @@ -60,14 +60,14 @@ func TestFileOutput_Send(t *testing.T) { assert.Nil(t, existsErr) if existsErr == nil { - os.Remove(path) + _ = os.Remove(path) } _, existsCSVErr := os.Stat(pathcsv) assert.Nil(t, existsCSVErr) if existsErr == nil { - os.Remove(pathcsv) + _ = os.Remove(pathcsv) } } diff --git a/pkg/output/output.go b/pkg/output/output.go index 95aed96..b14faa9 100644 --- a/pkg/output/output.go +++ b/pkg/output/output.go @@ -143,10 +143,10 @@ func (output Output) ToFile(filename string) error { } case ".csv": file, err := os.Create(filename) - defer file.Close() if err != nil { return err } + defer func() { _ = file.Close() }() w := csv.NewWriter(file) defer w.Flush() header := []string{"Release Name", "Chart Name", "Namespace", "HelmVersion", "Installed", "Latest", "Old", "Deprecated"} @@ -156,9 +156,9 @@ func (output Output) ToFile(filename string) error { row := []string{rl.ReleaseName, rl.ChartName, rl.Namespace, rl.HelmVersion, rl.Installed.Version, rl.Latest.Version, strconv.FormatBool(rl.IsOld), strconv.FormatBool(rl.Deprecated)} data = append(data, row) } - w.WriteAll(data) + _ = w.WriteAll(data) default: - return errors.New("File format is not supported. The supported file format are json and csv only") + return errors.New("file format is not supported. The supported file format are json and csv only") } return nil } @@ -172,7 +172,7 @@ func (output Output) Print(format string, wide, showOld bool) { switch format { case JSONFormat: data, _ := marshalWithoutHTMLEscaping(output.HelmReleases) - fmt.Fprintln(os.Stdout, string(data)) + _, _ = fmt.Fprintln(os.Stdout, string(data)) case TableFormat: w := tabwriter.NewWriter(os.Stdout, 0, 4, 4, ' ', 0) header := "Release Name\t" @@ -180,13 +180,13 @@ func (output Output) Print(format string, wide, showOld bool) { header += "Chart Name\tNamespace\tHelmVersion\t" } header += "Installed\tLatest\tOld\tDeprecated" - fmt.Fprintln(w, header) + _, _ = fmt.Fprintln(w, header) separator := "============\t" if wide { separator += "==========\t=========\t===========\t" } separator += "=========\t======\t===\t==========" - fmt.Fprintln(w, separator) + _, _ = fmt.Fprintln(w, separator) for _, release := range output.HelmReleases { if (!output.IncludeAll && release.Latest.Version == "") || (showOld && !release.IsOld) { @@ -202,9 +202,9 @@ func (output Output) Print(format string, wide, showOld bool) { line += release.Latest.Version + "\t" line += fmt.Sprintf("%t", release.IsOld) + "\t" line += fmt.Sprintf("%t", release.Deprecated) + "\t" - fmt.Fprintln(w, line) + _, _ = fmt.Fprintln(w, line) } - w.Flush() + _ = w.Flush() default: klog.Errorf("Output format is not supported. The supported formats are json and table only") } @@ -289,14 +289,14 @@ func (output ContainersOutput) Print(format string) { switch format { case JSONFormat: data, _ := marshalWithoutHTMLEscaping(output) - fmt.Fprintln(os.Stdout, string(data)) + _, _ = fmt.Fprintln(os.Stdout, string(data)) case TableFormat: w := tabwriter.NewWriter(os.Stdout, 0, 4, 4, ' ', 0) if len(output.ContainerImages) != 0 { header := "Container Name\tCurrent Version\tOld\tLatest\tLatest Minor\tLatest Patch" - fmt.Fprintln(w, header) + _, _ = fmt.Fprintln(w, header) separator := "==============\t===============\t===\t======\t=============\t=============" - fmt.Fprintln(w, separator) + _, _ = fmt.Fprintln(w, separator) for _, c := range output.ContainerImages { if !output.IncludeAll && c.LatestVersion == c.CurrentVersion { @@ -308,25 +308,25 @@ func (output ContainersOutput) Print(format string) { line += c.LatestVersion + "\t" line += c.LatestMinorVersion + "\t" line += c.LatestPatchVersion + "\t" - fmt.Fprintln(w, line) + _, _ = fmt.Fprintln(w, line) } } if len(output.ErrImages) == 0 { - w.Flush() + _ = w.Flush() return } - fmt.Fprintln(w, "\n\nErrors:") + _, _ = fmt.Fprintln(w, "\n\nErrors:") errHeader := "Container Name\tError" - fmt.Fprintln(w, errHeader) + _, _ = fmt.Fprintln(w, errHeader) errSeparator := "==============\t=====" - fmt.Fprintln(w, errSeparator) + _, _ = fmt.Fprintln(w, errSeparator) for _, e := range output.ErrImages { line := e.Image + "\t" line += e.Err + "\t" - fmt.Fprintln(w, line) + _, _ = fmt.Fprintln(w, line) } - w.Flush() + _ = w.Flush() if output.LatestStringFound { fmt.Printf("Found a container utilizing the 'latest' tag. This is bad practice and should be avoided.\n\n") } @@ -376,7 +376,7 @@ func (output HelmAndContainersOutput) Print(format string, wide, showOld bool) { IncludeAll: output.Helm.IncludeAll, } data, _ := marshalWithoutHTMLEscaping(outputFormat) - fmt.Fprintln(os.Stdout, string(data)) + _, _ = fmt.Fprintln(os.Stdout, string(data)) } } @@ -408,7 +408,7 @@ func (output HelmAndContainersOutput) ToFile(filename string) error { klog.Errorf("Error writing to file %s: %v", filename, err) } default: - return errors.New("File format is not supported. The supported file format is json only") + return errors.New("file format is not supported. The supported file format is json only") } return nil }