-
Notifications
You must be signed in to change notification settings - Fork 730
OCPBUGS-99434: Show toast notification for invalid Helm Chart repositories #16792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7c439bc
4fa0d7b
880ecdb
f17a539
4d910f8
f5a4f88
0bcda93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,6 @@ import ( | |
| "context" | ||
| "crypto/tls" | ||
| "crypto/x509" | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
|
|
@@ -41,9 +40,13 @@ var ( | |
| const ( | ||
| configNamespace = "openshift-config" | ||
| warning = "console-warning" | ||
| ErrorMessage = "The following repositories seem to be invalid or unreachable: " | ||
| ) | ||
|
|
||
| type InvalidRepo struct { | ||
| Name string `json:"name"` | ||
| Error string `json:"error"` | ||
| } | ||
|
|
||
| type helmRepo struct { | ||
| Name string | ||
| Namespace string | ||
|
|
@@ -84,8 +87,12 @@ func (hr helmRepo) IndexFile() (*repo.IndexFile, error) { | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer resp.Body.Close() | ||
| if resp.StatusCode == 401 || resp.StatusCode == 403 { | ||
| return nil, fmt.Errorf("authentication failed (HTTP %d)", resp.StatusCode) | ||
| } | ||
| if resp.StatusCode != 200 { | ||
| return nil, errors.New(fmt.Sprintf("Response for %v returned %v with status code %v", indexURL, resp, resp.StatusCode)) | ||
| return nil, fmt.Errorf("failed to fetch index (HTTP %d)", resp.StatusCode) | ||
| } | ||
| body, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
|
|
@@ -110,7 +117,7 @@ func (hr helmRepo) IndexFile() (*repo.IndexFile, error) { | |
| } | ||
|
|
||
| type HelmRepoGetter interface { | ||
| List(namespace string) ([]*helmRepo, error) | ||
| List(namespace string) ([]*helmRepo, []InvalidRepo, error) | ||
| } | ||
|
|
||
| type helmRepoGetter struct { | ||
|
|
@@ -254,40 +261,50 @@ func (b helmRepoGetter) unmarshallConfig(repo unstructured.Unstructured, namespa | |
| return h, nil | ||
| } | ||
|
|
||
| func (b *helmRepoGetter) List(namespace string) ([]*helmRepo, error) { | ||
| func (b *helmRepoGetter) processRepoItems(items []unstructured.Unstructured, namespace string, isClusterScoped bool) ([]*helmRepo, []InvalidRepo) { | ||
| var repos []*helmRepo | ||
| var configErrors []InvalidRepo | ||
| for _, item := range items { | ||
| helmConfig, err := b.unmarshallConfig(item, namespace, isClusterScoped) | ||
| if err != nil { | ||
| repoName, found, nameErr := unstructured.NestedString(item.Object, "metadata", "name") | ||
| if nameErr != nil || !found { | ||
| repoName = "<unknown>" | ||
| } | ||
| klog.Errorf("Error unmarshalling repo %v: %v", item, err) | ||
| configErrors = append(configErrors, InvalidRepo{Name: repoName, Error: err.Error()}) | ||
| continue | ||
| } | ||
| repos = append(repos, helmConfig) | ||
| } | ||
| return repos, configErrors | ||
| } | ||
|
|
||
| func (b *helmRepoGetter) List(namespace string) ([]*helmRepo, []InvalidRepo, error) { | ||
| var helmRepos []*helmRepo | ||
| var configErrors []InvalidRepo | ||
|
|
||
| clusterRepos, err := b.Client.Resource(helmChartRepositoryClusterGVK).List(context.TODO(), v1.ListOptions{}) | ||
| if err != nil { | ||
| klog.Errorf("Error listing cluster helm chart repositories: %v \nempty repository list will be used", err) | ||
| return helmRepos, nil | ||
| } | ||
| for _, item := range clusterRepos.Items { | ||
| helmConfig, err := b.unmarshallConfig(item, "", true) | ||
| if err != nil { | ||
| klog.Errorf("Error unmarshalling repo %v: %v", item, err) | ||
| continue | ||
| } | ||
| helmRepos = append(helmRepos, helmConfig) | ||
| return helmRepos, configErrors, nil | ||
| } | ||
| repos, errs := b.processRepoItems(clusterRepos.Items, "", true) | ||
| helmRepos = append(helmRepos, repos...) | ||
| configErrors = append(configErrors, errs...) | ||
|
|
||
| if namespace != "" { | ||
| namespaceRepos, err := b.Client.Resource(helmChartRepositoryNamespaceGVK).Namespace(namespace).List(context.TODO(), v1.ListOptions{}) | ||
| if err != nil { | ||
| klog.Errorf("Error listing namespace helm chart repositories: %v \nempty repository list will be used", err) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It hardly matters, but, assuming that In general, there's very little value in having the log message "predict" what will happen, because, when the log is read, it will have already happened. And, the developer can look at the code where the message was produced and see what it would have done. So, the second half of this log message is pointless. If you have to make an edit for some other reason, feel free to remove it (here and at line 289, although that one, while also largely pointless, is at least accurate 🙂). |
||
| return helmRepos, nil | ||
| } | ||
| for _, item := range namespaceRepos.Items { | ||
| helmConfig, err := b.unmarshallConfig(item, namespace, false) | ||
| if err != nil { | ||
| klog.Errorf("Error unmarshalling repo %v: %v", item, err) | ||
| continue | ||
| } | ||
| helmRepos = append(helmRepos, helmConfig) | ||
| return helmRepos, configErrors, nil | ||
| } | ||
| repos, errs := b.processRepoItems(namespaceRepos.Items, namespace, false) | ||
| helmRepos = append(helmRepos, repos...) | ||
| configErrors = append(configErrors, errs...) | ||
| } | ||
|
|
||
| return helmRepos, nil | ||
| return helmRepos, configErrors, nil | ||
| } | ||
|
|
||
| func NewRepoGetter(client dynamic.Interface, corev1Client corev1.CoreV1Interface) HelmRepoGetter { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,7 +111,7 @@ func TestHelmRepoGetter_List(t *testing.T) { | |
| }) | ||
| } | ||
| repoGetter := NewRepoGetter(client, nil) | ||
| repos, err := repoGetter.List(tt.namespace) | ||
| repos, _, err := repoGetter.List(tt.namespace) | ||
| if err != nil { | ||
| t.Error(err) | ||
| } | ||
|
|
@@ -136,11 +136,12 @@ func TestHelmRepoGetter_List(t *testing.T) { | |
|
|
||
| func TestHelmRepoGetter_ListErrors(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| helmCRS []*unstructured.Unstructured | ||
| expectedRepoName []string | ||
| apiErrors []apiError | ||
| namespace string | ||
| name string | ||
| helmCRS []*unstructured.Unstructured | ||
| expectedRepoName []string | ||
| expectedConfigErrorRepo []string | ||
| apiErrors []apiError | ||
| namespace string | ||
| }{ | ||
| { | ||
| name: "skip repo that refer non-existent config map", | ||
|
|
@@ -179,7 +180,8 @@ func TestHelmRepoGetter_ListErrors(t *testing.T) { | |
| }, | ||
| }, | ||
| }, | ||
| expectedRepoName: []string{"repo2"}, | ||
| expectedRepoName: []string{"repo2"}, | ||
| expectedConfigErrorRepo: []string{"repo1"}, | ||
| }, | ||
| { | ||
| name: "skip repo that refer config map that cannot be accessed", | ||
|
|
@@ -225,7 +227,8 @@ func TestHelmRepoGetter_ListErrors(t *testing.T) { | |
| msg: "foo", | ||
| }, | ||
| }, | ||
| expectedRepoName: []string{"repo2"}, | ||
| expectedRepoName: []string{"repo2"}, | ||
| expectedConfigErrorRepo: []string{"repo1"}, | ||
| }, | ||
| { | ||
| name: "skip repo that refer secret that cannot be accessed", | ||
|
|
@@ -271,7 +274,8 @@ func TestHelmRepoGetter_ListErrors(t *testing.T) { | |
| msg: "foo", | ||
| }, | ||
| }, | ||
| expectedRepoName: []string{"repo2"}, | ||
| expectedRepoName: []string{"repo2"}, | ||
| expectedConfigErrorRepo: []string{"repo1"}, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
|
|
@@ -284,7 +288,7 @@ func TestHelmRepoGetter_ListErrors(t *testing.T) { | |
| }) | ||
| } | ||
| repoGetter := NewRepoGetter(client, coreClient.CoreV1()) | ||
| repos, err := repoGetter.List(tt.namespace) | ||
| repos, configErrors, err := repoGetter.List(tt.namespace) | ||
| if err != nil { | ||
| t.Error(err) | ||
| } | ||
|
|
@@ -296,6 +300,17 @@ func TestHelmRepoGetter_ListErrors(t *testing.T) { | |
| t.Errorf("Expected %v but got %v", repoName, repos[i].Name) | ||
| } | ||
| } | ||
| if len(configErrors) != len(tt.expectedConfigErrorRepo) { | ||
| t.Fatalf("Expected %v config errors, but got %v", len(tt.expectedConfigErrorRepo), len(configErrors)) | ||
| } | ||
| for i, expectedName := range tt.expectedConfigErrorRepo { | ||
| if configErrors[i].Name != expectedName { | ||
| t.Errorf("Expected config error repo %v but got %v", expectedName, configErrors[i].Name) | ||
| } | ||
| if configErrors[i].Error == "" { | ||
| t.Errorf("Expected non-empty error message for repo %v", expectedName) | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Comment on lines
+303
to
+313
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not to disagree with Coderabbitai or anything, but, if this test fails because the counts don't match, the next question that a developer is going to ask is, "What was the mismatch?", and the code isn't going to offer any help. 🫤 Ideally, you would do a "set difference" operation (or two), expecting there to be no difference, and reporting any "extra" ones, either unexpectedly present or absent. Comparing the counts is a quick and easy way to do the check, but it doesn't reveal any helpful information about the failure, which means that the subsequent diagnosis requires reproducing the problem under the debugger, which is potentially cumbersome. I'll also note that expecting the config errors to come out in a particular order seems fragile at best. (You've finessed this problem by ensuring that there is only ever one error, but you've set the test up to handle multiple...although you don't actually test that.) I don't have any good suggestions, but I might be inclined to drop the initial count check and to have a nested loop which searches for each expected error in the actual list (instead of assuming and requiring that it show up at a particular index): that would enable reporting any expected ones which were missing. If you put that into a helper function, you could then call it again with the arguments reversed to report any actual ones which weren't in the expected list. This would yield full information without too much code, but it's probably more trouble than it's worth...especially if you only expect one error. 😉 Alternatively, after ensuring that the expected ones were present, you could then use the count-based check to ensure that there weren't any extras...which would at least give you half the information. |
||
| }) | ||
| } | ||
| } | ||
|
|
@@ -499,7 +514,7 @@ func TestHelmRepoGetter_SkipDisabled(t *testing.T) { | |
| client := fake.K8sDynamicClientFromCRs(tt.helmCRS...) | ||
| coreClient := k8sfake.NewSimpleClientset() | ||
| repoGetter := NewRepoGetter(client, coreClient.CoreV1()) | ||
| repos, err := repoGetter.List(tt.namespace) | ||
| repos, _, err := repoGetter.List(tt.namespace) | ||
| if err != nil { | ||
| t.Error(err) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
json.Marshal()returns an error here, we silently ignore it and move on...which is kind of unsatisfying.If this is a "never happen" situation, then we should consider panicking if it does. Otherwise, maybe attach the error as an annotation, or log it, or somehow try to alert somebody that things aren't working?