Repository: helm/helm (go)

Issue:
No Custom Exported Error types in `pkg/repo`
I've noticed that when pulling a helm chart, there is no custom exported error types for common failure modes such as a "not found" error.

For example: 
https://github.com/helm/helm/blob/d8ca55fc669645c10c0681d49723f4bb8c0b1ce7/pkg/repo/chartrepo.go#L300

In this case, the builtin `errors.errorString` default implementation is returned, with a little decoration. 

This means there's no way to identify this common error mode using `errors.Is()` or `errors.As()`. 

I'm happy to open a PR for this. 

If there are any other common error modes that we can identify in this same package, I'd be happy to add a custom error type for these too (for example with authentication issues).

Pull request description:
ref(helm): Export Chart Not Found error
Closes #30746

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:
- Export an error type for "Chart Not Found" type errors for error type checking

**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains user facing changes (the `docs needed` label should be applied if so)
- [x] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility


Patch:
diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go
index 2667dc2b1..3b9f2bfea 100644
--- a/pkg/repo/chartrepo.go
+++ b/pkg/repo/chartrepo.go
@@ -219,7 +219,10 @@ func FindChartInRepoURL(repoURL string, chartName string, getters getter.Provide
 	}
 	cv, err := repoIndex.Get(chartName, opts.ChartVersion)
 	if err != nil {
-		return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL)
+		return "", ChartNotFoundError{
+			Chart:   errMsg,
+			RepoURL: repoURL,
+		}
 	}
 
 	if len(cv.URLs) == 0 {
diff --git a/pkg/repo/error.go b/pkg/repo/error.go
new file mode 100644
index 000000000..16264ed26
--- /dev/null
+++ b/pkg/repo/error.go
@@ -0,0 +1,35 @@
+/*
+Copyright The Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package repo
+
+import (
+	"fmt"
+)
+
+type ChartNotFoundError struct {
+	RepoURL string
+	Chart   string
+}
+
+func (e ChartNotFoundError) Error() string {
+	return fmt.Sprintf("%s not found in %s repository", e.Chart, e.RepoURL)
+}
+
+func (e ChartNotFoundError) Is(err error) bool {
+	_, ok := err.(ChartNotFoundError)
+	return ok
+}


Test patch:
diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go
index 41bac9827..c29c95a7e 100644
--- a/pkg/repo/chartrepo_test.go
+++ b/pkg/repo/chartrepo_test.go
@@ -18,6 +18,7 @@ package repo
 
 import (
 	"bytes"
+	"errors"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -202,6 +203,9 @@ func TestErrorFindChartInRepoURL(t *testing.T) {
 	} else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` {
 		t.Errorf("Expected error for chart not found, but got a different error (%v)", err)
 	}
+	if !errors.Is(err, ChartNotFoundError{}) {
+		t.Errorf("error is not of correct error type structure")
+	}
 
 	if _, err = FindChartInRepoURL(srv.URL, "nginx1", g, WithChartVersion("0.1.0")); err == nil {
 		t.Errorf("Expected error for chart not found, but did not get any errors")