-
Notifications
You must be signed in to change notification settings - Fork 542
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add GVK existence check for OLM supported resources by querying…
… discovery in case of 404 not found errors when applying installplan steps.
- Loading branch information
Showing
5 changed files
with
181 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package catalog | ||
|
||
import ( | ||
"fmt" | ||
|
||
"k8s.io/apimachinery/pkg/api/errors" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/client-go/discovery" | ||
|
||
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" | ||
) | ||
|
||
// gvkNotFoundError is returned from installplan execution when a step contains a GVK that is not found on cluster. | ||
type gvkNotFoundError struct { | ||
gvk schema.GroupVersionKind | ||
name string | ||
} | ||
|
||
func (g gvkNotFoundError) Error() string { | ||
return fmt.Sprintf("api-server resource not found installing %s %s: GroupVersionKind %s not found on the cluster. %s", g.gvk.Kind, g.name, g.gvk, | ||
"This API may have been deprecated and removed, see https://kubernetes.io/docs/reference/using-api/deprecation-guide/ for more information.") | ||
} | ||
|
||
type DiscoveryQuerier interface { | ||
QueryForGVK() error | ||
} | ||
|
||
type DiscoveryQuerierFunc func() error | ||
|
||
func (d DiscoveryQuerierFunc) QueryForGVK() error { | ||
return d() | ||
} | ||
|
||
type discoveryQuerier struct { | ||
client discovery.DiscoveryInterface | ||
} | ||
|
||
func newDiscoveryQuerier(client discovery.DiscoveryInterface) *discoveryQuerier { | ||
return &discoveryQuerier{client: client} | ||
} | ||
|
||
// WithStepResource returns a DiscoveryQuerier which uses discovery to query for supported APIs on the server based on the provided step's GVK. | ||
func (d *discoveryQuerier) WithStepResource(stepResource operatorsv1alpha1.StepResource) DiscoveryQuerier { | ||
var f DiscoveryQuerierFunc = func() error { | ||
gvk := schema.GroupVersionKind{Group: stepResource.Group, Version: stepResource.Version, Kind: stepResource.Kind} | ||
|
||
resourceList, err := d.client.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) | ||
if err != nil { | ||
if errors.IsNotFound(err) { | ||
return gvkNotFoundError{gvk: gvk, name: stepResource.Name} | ||
} | ||
return err | ||
} | ||
|
||
if resourceList == nil { | ||
return gvkNotFoundError{gvk: gvk, name: stepResource.Name} | ||
} | ||
|
||
for _, resource := range resourceList.APIResources { | ||
if resource.Kind == stepResource.Kind { | ||
// this kind is supported for this particular GroupVersion | ||
return nil | ||
} | ||
} | ||
|
||
return gvkNotFoundError{gvk: gvk, name: stepResource.Name} | ||
} | ||
return f | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,89 @@ | ||
package e2e | ||
|
||
// TODO | ||
import ( | ||
"context" | ||
"time" | ||
|
||
// v1beta1 CRD in installplan fails | ||
// v1beta1 RBAC in an installplan fails | ||
"github.com/blang/semver/v4" | ||
. "github.com/onsi/ginkgo" | ||
"github.com/onsi/ginkgo/extensions/table" | ||
. "github.com/onsi/gomega" | ||
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" | ||
) | ||
|
||
var missingAPI = `{"apiVersion":"verticalpodautoscalers.autoscaling.k8s.io/v1","kind":"VerticalPodAutoscaler","metadata":{"name":"my.thing","namespace":"foo"}}` | ||
|
||
var _ = Describe("Not found APIs", func() { | ||
BeforeEach(func() { | ||
csv := newCSV("test-csv", testNamespace, "", semver.Version{}, nil, nil, nil) | ||
Expect(ctx.Ctx().Client().Create(context.TODO(), &csv)).To(Succeed()) | ||
}) | ||
AfterEach(func() { | ||
TearDown(testNamespace) | ||
}) | ||
|
||
When("objects with APIs that are not on-cluster are created in the installplan", func() { | ||
// each entry is an installplan with a deprecated resource | ||
type payload struct { | ||
name string | ||
ip *operatorsv1alpha1.InstallPlan | ||
errMessage string | ||
} | ||
|
||
var tableEntries []table.TableEntry | ||
tableEntries = []table.TableEntry{ | ||
table.Entry("contains an entry with a missing API not found on cluster ", payload{ | ||
name: "installplan contains a missing API", | ||
ip: &operatorsv1alpha1.InstallPlan{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: *namespace, // this is necessary due to ginkgo table semantics, see https://github.com/onsi/ginkgo/issues/378 | ||
Name: "test-plan-api", | ||
}, | ||
Spec: operatorsv1alpha1.InstallPlanSpec{ | ||
Approval: operatorsv1alpha1.ApprovalAutomatic, | ||
Approved: true, | ||
ClusterServiceVersionNames: []string{}, | ||
}, | ||
Status: operatorsv1alpha1.InstallPlanStatus{ | ||
Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, | ||
CatalogSources: []string{}, | ||
Plan: []*operatorsv1alpha1.Step{ | ||
{ | ||
Resolving: "test-csv", | ||
Status: operatorsv1alpha1.StepStatusUnknown, | ||
Resource: operatorsv1alpha1.StepResource{ | ||
Name: "my.thing", | ||
Group: "verticalpodautoscalers.autoscaling.k8s.io", | ||
Version: "v1", | ||
Kind: "VerticalPodAutoscaler", | ||
Manifest: missingAPI, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
errMessage: "api-server resource not found installing VerticalPodAutoscaler my.thing: GroupVersionKind " + | ||
"verticalpodautoscalers.autoscaling.k8s.io/v1, Kind=VerticalPodAutoscaler not found on the cluster", | ||
}), | ||
} | ||
|
||
table.DescribeTable("the ip enters a failed state with a helpful error message", func(tt payload) { | ||
Expect(ctx.Ctx().Client().Create(context.Background(), tt.ip)).To(Succeed()) | ||
Expect(ctx.Ctx().Client().Status().Update(context.Background(), tt.ip)).To(Succeed()) | ||
|
||
// The IP sits in the Installing phase with the GVK missing error | ||
Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { | ||
return tt.ip, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(tt.ip), tt.ip) | ||
}).Should(And(HavePhase(operatorsv1alpha1.InstallPlanPhaseInstalling)), HaveMessage(tt.errMessage)) | ||
|
||
// Eventually the IP fails with the GVK missing error, after installplan retries, which is by default 1 minute. | ||
Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { | ||
return tt.ip, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(tt.ip), tt.ip) | ||
}, 2*time.Minute).Should(And(HavePhase(operatorsv1alpha1.InstallPlanPhaseFailed)), HaveMessage(tt.errMessage)) | ||
}, tableEntries...) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters