-
Notifications
You must be signed in to change notification settings - Fork 31
OPRUN-3941: [OTE] Add webhook tests (without should be tolerant to openshift-service-ca certificate rotation) #442
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
Changes from all commits
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 |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package helpers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
//nolint:staticcheck // ST1001: dot-imports for readability | ||
. "github.com/onsi/gomega" | ||
|
||
"k8s.io/apimachinery/pkg/api/meta" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
olmv1 "github.com/operator-framework/operator-controller/api/v1" | ||
|
||
"github/operator-framework-operator-controller/openshift/tests-extension/pkg/env" | ||
) | ||
|
||
// NewClusterCatalog returns a new ClusterCatalog object. | ||
// It sets the image reference as source. | ||
func NewClusterCatalog(name, imageRef string) *olmv1.ClusterCatalog { | ||
return &olmv1.ClusterCatalog{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
}, | ||
Spec: olmv1.ClusterCatalogSpec{ | ||
Source: olmv1.CatalogSource{ | ||
Type: olmv1.SourceTypeImage, | ||
Image: &olmv1.ImageSource{ | ||
Ref: imageRef, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
// ExpectCatalogToBeServing checks that the catalog with the given name is installed | ||
func ExpectCatalogToBeServing(ctx context.Context, name string) { | ||
k8sClient := env.Get().K8sClient | ||
Eventually(func(g Gomega) { | ||
var catalog olmv1.ClusterCatalog | ||
err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, &catalog) | ||
g.Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("failed to get catalog %q", name)) | ||
|
||
conditions := catalog.Status.Conditions | ||
g.Expect(conditions).NotTo(BeEmpty(), fmt.Sprintf("catalog %q has empty status.conditions", name)) | ||
|
||
g.Expect(meta.IsStatusConditionPresentAndEqual(conditions, olmv1.TypeServing, metav1.ConditionTrue)). | ||
To(BeTrue(), fmt.Sprintf("catalog %q is not serving", name)) | ||
}).WithTimeout(5 * time.Minute).WithPolling(5 * time.Second).Should(Succeed()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package helpers | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os/exec" | ||
"strings" | ||
|
||
//nolint:staticcheck // ST1001: dot-imports for readability | ||
. "github.com/onsi/ginkgo/v2" | ||
) | ||
|
||
// findK8sTool returns "oc" if available, otherwise "kubectl". | ||
// If we are running locally we either prefer to use oc since some tests | ||
// require it, or fallback to kubectl if oc is not available. | ||
func findK8sTool() (string, error) { | ||
tools := []string{"oc", "kubectl"} | ||
for _, t := range tools { | ||
// First check if the tool is available in the PATH. | ||
if _, err := exec.LookPath(t); err != nil { | ||
continue | ||
} | ||
// Verify that the tool is working by checking its version. | ||
if err := exec.Command(t, "version", "--client").Run(); err == nil { | ||
return t, nil | ||
} | ||
} | ||
return "", fmt.Errorf("no Kubernetes CLI client found (tried %s)", | ||
strings.Join(tools, ", ")) | ||
} | ||
|
||
// RunK8sCommand runs a Kubernetes CLI command and returns ONLY stdout. | ||
// If the command fails, stderr is included in the returned error (not mixed with stdout). | ||
func RunK8sCommand(ctx context.Context, args ...string) ([]byte, error) { | ||
tool, err := findK8sTool() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cmd := exec.CommandContext(ctx, tool, args...) | ||
out, err := cmd.Output() | ||
if err != nil { | ||
var ee *exec.ExitError | ||
if errors.As(err, &ee) { | ||
stderr := strings.TrimSpace(string(ee.Stderr)) | ||
if stderr != "" { | ||
return nil, fmt.Errorf("%s %s failed: %w\nstderr:\n%s", | ||
tool, strings.Join(args, " "), err, stderr) | ||
} | ||
} | ||
return nil, fmt.Errorf("%s %s failed: %w", | ||
tool, strings.Join(args, " "), err) | ||
} | ||
return out, nil | ||
} | ||
|
||
// RunAndPrint runs a `kubectl/oc` command via RunK8sCommand and writes both stdout and stderr | ||
// to the GinkgoWriter. It also prints the exact command being run. | ||
func RunAndPrint(ctx context.Context, args ...string) { | ||
fmt.Fprintf(GinkgoWriter, "\n[diag] running: %s\n", | ||
strings.Join(quoteArgs(args), " ")) | ||
out, err := RunK8sCommand(ctx, args...) | ||
if err != nil { | ||
fmt.Fprintf(GinkgoWriter, "[diag] command failed: %v\n", err) | ||
} | ||
if len(out) > 0 { | ||
fmt.Fprintf(GinkgoWriter, "%s\n", string(out)) | ||
} | ||
} | ||
|
||
func quoteArgs(args []string) []string { | ||
quoted := make([]string, len(args)) | ||
for i, a := range args { | ||
// Add quotes only if whitespace or special chars are present | ||
if strings.ContainsAny(a, " \t") { | ||
quoted[i] = fmt.Sprintf("%q", a) | ||
} else { | ||
quoted[i] = a | ||
} | ||
Comment on lines
+75
to
+80
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. +1 |
||
} | ||
return quoted | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package helpers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
//nolint:staticcheck // ST1001: dot-imports for readability | ||
. "github.com/onsi/ginkgo/v2" | ||
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. The alternative would be to use 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. IHMO the g is hard to understand because one we need to add g other o. But we are doing it in all files (not only on those), while I agree that can be debatable can we agree taht if we change that would be in a follow up so we can change all places? |
||
) | ||
|
||
func sectionHeader(format string, a ...any) { | ||
fmt.Fprintf(GinkgoWriter, "\n=== %s ===\n", fmt.Sprintf(format, a...)) | ||
} | ||
|
||
func subHeader(format string, a ...any) { | ||
fmt.Fprintf(GinkgoWriter, "\n--- %s ---\n", fmt.Sprintf(format, a...)) | ||
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. Nit: I almost want the 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. Done 👍 |
||
} | ||
|
||
// GetAllPodLogs prints logs for all containers in all pods in the given namespace. | ||
func GetAllPodLogs(ctx context.Context, namespace string) { | ||
sectionHeader("[pod-logs] namespace=%s", namespace) | ||
|
||
By("listing pods in namespace " + namespace) | ||
namesOut, err := RunK8sCommand(ctx, "get", "pods", "-n", namespace, "-o", "name") | ||
if err != nil { | ||
fmt.Fprintf(GinkgoWriter, "failed to list pods: %v\n%s\n", err, string(namesOut)) | ||
return | ||
} | ||
lines := strings.Fields(strings.TrimSpace(string(namesOut))) | ||
if len(lines) == 0 { | ||
fmt.Fprintln(GinkgoWriter, "(no pods found)") | ||
return | ||
} | ||
|
||
for _, res := range lines { | ||
subHeader("logs for %s", res) | ||
logsOut, err := RunK8sCommand( | ||
ctx, | ||
"logs", | ||
"-n", namespace, | ||
"--all-containers", | ||
"--prefix", | ||
"--timestamps", | ||
res, | ||
) | ||
if err != nil { | ||
fmt.Fprintf(GinkgoWriter, "error fetching logs for %s: %v\n%s\n", res, err, string(logsOut)) | ||
continue | ||
} | ||
_, _ = GinkgoWriter.Write(logsOut) // ignore write error by design | ||
} | ||
fmt.Fprintln(GinkgoWriter) | ||
} | ||
|
||
// DescribePods prints the `kubectl/oc describe pods` output for all pods in a given namespace. | ||
func DescribePods(ctx context.Context, namespace string) { | ||
sectionHeader("[describe pods] namespace=%s", namespace) | ||
RunAndPrint(ctx, "describe", "pods", "-n", namespace) | ||
} | ||
|
||
// DescribeAllClusterCatalogs lists all ClusterCatalogs and runs `describe` on each. | ||
func DescribeAllClusterCatalogs(ctx context.Context) { | ||
sectionHeader("[cluster catalogs]") | ||
|
||
out, err := RunK8sCommand(ctx, "get", "clustercatalogs", "-o", "name") | ||
if err != nil { | ||
fmt.Fprintf(GinkgoWriter, "failed to list clustercatalogs: %v\n", err) | ||
return | ||
} | ||
|
||
catalogs := strings.Fields(strings.TrimSpace(string(out))) | ||
if len(catalogs) == 0 { | ||
fmt.Fprintln(GinkgoWriter, "(no clustercatalogs found)") | ||
RunAndPrint(ctx, "get", "clustercatalogs") | ||
return | ||
} | ||
|
||
for _, catalog := range catalogs { | ||
subHeader("describe %s", catalog) | ||
RunAndPrint(ctx, "describe", catalog) | ||
} | ||
fmt.Fprintln(GinkgoWriter) | ||
} | ||
|
||
// DescribeAllClusterExtensions describes every ClusterExtension in the given namespace. | ||
func DescribeAllClusterExtensions(ctx context.Context, namespace string) { | ||
if namespace == "" { | ||
return | ||
} | ||
sectionHeader("[clusterextensions] namespace=%s", namespace) | ||
|
||
args := []string{"get", "clusterextensions", "-n", namespace, "-o", "name"} | ||
out, err := RunK8sCommand(ctx, args...) | ||
if err != nil { | ||
fmt.Fprintf(GinkgoWriter, "failed to list clusterextensions: %v\n", err) | ||
RunAndPrint(ctx, args...) | ||
return | ||
} | ||
|
||
names := strings.Fields(strings.TrimSpace(string(out))) | ||
if len(names) == 0 { | ||
fmt.Fprintln(GinkgoWriter, "(no clusterextensions found)") | ||
RunAndPrint(ctx, args...) | ||
return | ||
} | ||
|
||
for _, n := range names { | ||
subHeader("describe %s", n) | ||
RunAndPrint(ctx, "describe", "clusterextension", strings.TrimPrefix(n, "clusterextension/"), "-n", namespace) | ||
} | ||
fmt.Fprintln(GinkgoWriter) | ||
} |
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. The changes to this file are not strictly related to the subject of this PR. It is mostly some cleanup using other APIs (i.e. to find a command-line client), to avoid duplication. The changes are fine. So, while it may not belong as part of this PR, I'm not going to push for it to be separated. 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. Also, this could've used some of the new helpers that were written in this PR (e.g. 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. (Admittedly, I did write this fairly independently of the rest of the code, where there wasn't that much infrastructure in-place.) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,10 +60,6 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 | |
} | ||
By(fmt.Sprintf("testing against OCP %s", testVersion)) | ||
|
||
By("finding a k8s client") | ||
cmdLine, err := getK8sCommandLineClient() | ||
Expect(err).To(Succeed()) | ||
|
||
By("creating a new Namespace") | ||
nsCleanup := createNamespace(nsName) | ||
DeferCleanup(nsCleanup) | ||
|
@@ -85,7 +81,7 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 | |
DeferCleanup(fileCleanup) | ||
By(fmt.Sprintf("created operator tarball %q", fileOperator)) | ||
|
||
By(fmt.Sprintf("starting the operator build with %q via RAW URL", cmdLine)) | ||
By("starting the operator build via RAW URL") | ||
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. what is RAW URL? 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. A RAW URL usually means a direct link to the unprocessed (raw) content of a file, without any extra formatting, preview, or surrounding interface. Example:
The change here is just because we are using the utils, so we don't need or have cmdLine anymore. 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. Requested in the PR to add the follow ups (which is in hold because we cannot add the test that fails): #430 (comment) 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. In this particular case, it is invoking a k8s API via a URL (using |
||
opArgs := []string{ | ||
"create", | ||
"--raw", | ||
|
@@ -96,7 +92,7 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 | |
"-f", | ||
fileOperator, | ||
} | ||
buildOperator := startBuild(cmdLine, opArgs...) | ||
buildOperator := startBuild(opArgs...) | ||
|
||
By(fmt.Sprintf("waiting for the build %q to finish", buildOperator.Name)) | ||
waitForBuildToFinish(ctx, buildOperator.Name, nsName) | ||
|
@@ -114,7 +110,7 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 | |
DeferCleanup(fileCleanup) | ||
By(fmt.Sprintf("created catalog tarball %q", fileCatalog)) | ||
|
||
By(fmt.Sprintf("starting the catalog build with %q via RAW URL", cmdLine)) | ||
By("starting the catalog build via RAW URL") | ||
catalogArgs := []string{ | ||
"create", | ||
"--raw", | ||
|
@@ -125,7 +121,7 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 | |
"-f", | ||
fileCatalog, | ||
} | ||
buildCatalog := startBuild(cmdLine, catalogArgs...) | ||
buildCatalog := startBuild(catalogArgs...) | ||
|
||
By(fmt.Sprintf("waiting for the build %q to finish", buildCatalog.Name)) | ||
waitForBuildToFinish(ctx, buildCatalog.Name, nsName) | ||
|
@@ -386,18 +382,10 @@ func waitForClusterOperatorUpgradable(ctx SpecContext, name string) { | |
}).WithTimeout(5 * time.Minute).WithPolling(1 * time.Second).Should(Succeed()) | ||
} | ||
|
||
func getK8sCommandLineClient() (string, error) { | ||
s, err := exec.LookPath("kubectl") | ||
if err != nil { | ||
s, err = exec.LookPath("oc") | ||
} | ||
return s, err | ||
} | ||
|
||
func startBuild(cmdLine string, args ...string) *buildv1.Build { | ||
cmd := exec.Command(cmdLine, args...) | ||
output, err := cmd.Output() | ||
func startBuild(args ...string) *buildv1.Build { | ||
output, err := helpers.RunK8sCommand(context.Background(), args...) | ||
Expect(err).To(Succeed(), printExitError(err)) | ||
|
||
/* The output is JSON of a build.build.openshift.io resource */ | ||
build := &buildv1.Build{} | ||
Expect(json.Unmarshal(output, build)).To(Succeed(), "failed to unmarshal build") | ||
|
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.
Not sure this is strictly necessary, but it does ensure that the client can be executed.
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.
I think is good right to ensure that all is fine mainly because we can either run locally