Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion test/internal/e2e/steps/asciicast_recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ import (

type recorderContextKey struct{}

// WithRecorder attaches rec as both the demo asciicast recorder and the command
// observer used by shared helpers, including bash and K8sClient. Both roles
// intentionally use the same instance; callers must not override the command
// recorder separately on the returned context.
func WithRecorder(ctx context.Context, rec *AsciiCastRecorder) context.Context {
return context.WithValue(ctx, recorderContextKey{}, rec)
return context.WithValue(WithCommandRecorder(ctx, rec), recorderContextKey{}, rec)
}

func RecorderFromContext(ctx context.Context) *AsciiCastRecorder {
Expand Down
22 changes: 22 additions & 0 deletions test/internal/e2e/steps/asciicast_recorder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package steps_test

import (
"context"
"testing"

"github.com/stretchr/testify/require"

"github.com/operator-framework/operator-controller/test/internal/e2e/steps"
)

func TestWithRecorder(t *testing.T) {
parent := steps.WithCommandRecorder(context.Background(), &steps.AsciiCastRecorder{})
recorder := &steps.AsciiCastRecorder{}

ctx := steps.WithRecorder(parent, recorder)

require.Same(t, recorder, steps.RecorderFromContext(ctx))
require.Same(t, recorder, steps.CommandRecorderFromContext(ctx))
require.NotSame(t, recorder, steps.CommandRecorderFromContext(parent))
require.Nil(t, steps.RecorderFromContext(parent))
}
32 changes: 32 additions & 0 deletions test/internal/e2e/steps/command_recorder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package steps_test

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/operator-framework/operator-controller/test/internal/e2e/steps"
)

type fakeCommandRecorder struct{}

func (*fakeCommandRecorder) RecordCommand(string, string, string, time.Duration) {}
func (*fakeCommandRecorder) RecordCommandWithInput(string, string, string, string, time.Duration) {
}
func (*fakeCommandRecorder) RecordCustom(string, string, string) {}

func TestCommandRecorderFromContextWithoutRecorder(t *testing.T) {
require.Nil(t, steps.CommandRecorderFromContext(context.Background()))
}

func TestWithCommandRecorder(t *testing.T) {
parent := context.Background()
recorder := &fakeCommandRecorder{}

ctx := steps.WithCommandRecorder(parent, recorder)

require.Same(t, recorder, steps.CommandRecorderFromContext(ctx))
require.Nil(t, steps.CommandRecorderFromContext(parent))
}
14 changes: 7 additions & 7 deletions test/internal/e2e/steps/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ type resourceConditions struct {
} `json:"status"`
}

func readResourceConditions(ctx context.Context, resourceType, resourceName string) (*resourceConditions, error) {
out, err := k8sClient(ctx, "get", resourceType, resourceName, "-o", "json")
func ReadResourceConditions(ctx context.Context, resourceType, resourceName string) (*resourceConditions, error) {
out, err := K8sClient(ctx, "get", resourceType, resourceName, "-o", "json")
if err != nil {
return nil, fmt.Errorf("reading %s %q: %w", resourceType, resourceName, err)
}
Expand All @@ -30,21 +30,21 @@ func readResourceConditions(ctx context.Context, resourceType, resourceName stri
return &resource, nil
}

func generationObserved(generation, observedGeneration int64) bool {
func GenerationObserved(generation, observedGeneration int64) bool {
return generation > 0 && observedGeneration == generation
}

// currentCondition returns only the requested condition, and only if it reflects
// CurrentCondition returns only the requested condition, and only if it reflects
// the current generation. An unrelated up-to-date condition is not sufficient.
func currentCondition(generation int64, conditions []metav1.Condition, conditionType string) *metav1.Condition {
func CurrentCondition(generation int64, conditions []metav1.Condition, conditionType string) *metav1.Condition {
condition := apimeta.FindStatusCondition(conditions, conditionType)
if condition == nil || !generationObserved(generation, condition.ObservedGeneration) {
if condition == nil || !GenerationObserved(generation, condition.ObservedGeneration) {
return nil
}
return condition
}

func conditionMatches(condition *metav1.Condition, conditionStatus metav1.ConditionStatus, reason *string, msgCmp msgMatchFn) bool {
func ConditionMatches(condition *metav1.Condition, conditionStatus metav1.ConditionStatus, reason *string, msgCmp MsgMatchFn) bool {
return condition != nil && condition.Status == conditionStatus &&
(reason == nil || condition.Reason == *reason) &&
(msgCmp == nil || msgCmp(condition.Message))
Expand Down
70 changes: 35 additions & 35 deletions test/internal/e2e/steps/demo_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,25 @@ func bash(ctx context.Context, script string) (string, error) {
}
logger.V(1).Info("Output", "command", script, "output", stdout)

if rec := RecorderFromContext(ctx); rec != nil {
if rec := CommandRecorderFromContext(ctx); rec != nil {
rec.RecordCommand(script, stdout, stderr, elapsed)
}
return stdout, err
}

func CatalogReportsConditionWithoutReason(ctx context.Context, catalogUserName, conditionType, conditionStatus string) error {
sc := scenarioCtx(ctx)
catalogName, ok := sc.catalogs[catalogUserName]
sc := Scenario(ctx)
catalogName, ok := sc.Catalogs[catalogUserName]
if !ok {
if _, err := k8sClient(ctx, "get", "clustercatalog", catalogUserName); err != nil {
if _, err := K8sClient(ctx, "get", "clustercatalog", catalogUserName); err != nil {
return fmt.Errorf("catalog %q was not created by this scenario and does not exist on the cluster", catalogUserName)
}
catalogName = catalogUserName
}
err := waitForCondition(ctx, "clustercatalog", catalogName, conditionType, metav1.ConditionStatus(conditionStatus), nil, nil)
err := WaitForCondition(ctx, "clustercatalog", catalogName, conditionType, metav1.ConditionStatus(conditionStatus), nil, nil)
if err == nil {
if rec := RecorderFromContext(ctx); rec != nil {
out, _ := k8sClient(ctx, "get", "clustercatalog", catalogName,
if rec := CommandRecorderFromContext(ctx); rec != nil {
out, _ := K8sClient(ctx, "get", "clustercatalog", catalogName,
"-o", fmt.Sprintf("jsonpath={.status.conditions[?(@.type==\"%s\")]}", conditionType))
var pretty bytes.Buffer
if jsonErr := json.Indent(&pretty, []byte(out), "", " "); jsonErr == nil {
Expand All @@ -85,16 +85,16 @@ func CatalogReportsConditionWithoutReason(ctx context.Context, catalogUserName,
}

func ensureCatalogPortForward(ctx context.Context) (string, error) {
sc := scenarioCtx(ctx)
if sc.catalogAddr != "" {
if catalogPortForwardAlive(sc.catalogAddr) {
return sc.catalogAddr, nil
sc := Scenario(ctx)
if sc.CatalogAddr != "" {
if catalogPortForwardAlive(sc.CatalogAddr) {
return sc.CatalogAddr, nil
}
logger.V(1).Info("Catalog port-forward is dead, re-establishing", "addr", sc.catalogAddr)
logger.V(1).Info("Catalog port-forward is dead, re-establishing", "addr", sc.CatalogAddr)
resetCatalogPortForward(ctx)
}

ns := componentNamespaces["catalogd"]
ns := ComponentNamespaces["catalogd"]
target, err := catalogdLeaderPod(ctx, ns)
port := int32(443)
if err != nil {
Expand All @@ -108,17 +108,17 @@ func ensureCatalogPortForward(ctx context.Context) (string, error) {
if err != nil {
return "", fmt.Errorf("failed to start catalog port-forward to %s: %w", target, err)
}
sc.catalogAddr = addr
sc.catalogCleanup = cleanup
sc.CatalogAddr = addr
sc.CatalogCleanup = cleanup

waitFor(ctx, func() bool {
WaitFor(ctx, func() bool {
return catalogPortForwardAlive(addr)
})
return addr, nil
}

func catalogdLeaderPod(ctx context.Context, ns string) (string, error) {
holder, err := k8sClient(ctx, "get", "lease", "catalogd-operator-lock", "-n", ns,
holder, err := K8sClient(ctx, "get", "lease", "catalogd-operator-lock", "-n", ns,
"-o", "jsonpath={.spec.holderIdentity}")
if err != nil {
return "", fmt.Errorf("failed to get catalogd leader lease: %w", err)
Expand Down Expand Up @@ -156,12 +156,12 @@ func catalogPortForwardAlive(addr string) bool {
// CatalogdHA, non-leader pods return 404 (empty local cache); resetting
// lets the next retry potentially reach the leader pod.
func resetCatalogPortForward(ctx context.Context) {
sc := scenarioCtx(ctx)
if sc.catalogCleanup != nil {
sc.catalogCleanup()
sc := Scenario(ctx)
if sc.CatalogCleanup != nil {
sc.CatalogCleanup()
}
sc.catalogAddr = ""
sc.catalogCleanup = nil
sc.CatalogAddr = ""
sc.CatalogCleanup = nil
}

func catalogCurlJq(ctx context.Context, catalogName, jqFilter string) (string, error) {
Expand All @@ -185,7 +185,7 @@ func catalogCurlJq(ctx context.Context, catalogName, jqFilter string) (string, e
}

func CatalogContainsSomePackages(ctx context.Context, catalogName string) error {
waitFor(ctx, func() bool {
WaitFor(ctx, func() bool {
out, err := catalogCurlJq(ctx, catalogName,
`objects | select(.schema == "olm.package") | .name`)
if err != nil {
Expand All @@ -202,7 +202,7 @@ func CatalogContainsSomePackages(ctx context.Context, catalogName string) error
}

func PackageHasSomeChannels(ctx context.Context, packageName, catalogName string) error {
waitFor(ctx, func() bool {
WaitFor(ctx, func() bool {
out, err := catalogCurlJq(ctx, catalogName,
fmt.Sprintf(`objects | select(.schema == "olm.channel") | select(.package == "%s") | .name`, packageName))
if err != nil {
Expand All @@ -219,7 +219,7 @@ func PackageHasSomeChannels(ctx context.Context, packageName, catalogName string
}

func PackageHasSomeBundles(ctx context.Context, packageName, catalogName string) error {
waitFor(ctx, func() bool {
WaitFor(ctx, func() bool {
out, err := catalogCurlJq(ctx, catalogName,
fmt.Sprintf(`objects | select(.schema == "olm.bundle") | select(.package == "%s") | .name`, packageName))
if err != nil {
Expand All @@ -236,8 +236,8 @@ func PackageHasSomeBundles(ctx context.Context, packageName, catalogName string)
}

func RolebindingsReferenceServiceAccount(ctx context.Context, rbNamespace, saName, saNamespace string) error {
saNamespace = substituteScenarioVars(saNamespace, scenarioCtx(ctx))
out, err := k8sClient(ctx, "get", "rolebindings", "-n", rbNamespace, "-o", "json")
saNamespace = SubstituteScenarioVars(saNamespace, Scenario(ctx))
out, err := K8sClient(ctx, "get", "rolebindings", "-n", rbNamespace, "-o", "json")
if err != nil {
return fmt.Errorf("failed to list rolebindings in namespace %q: %w", rbNamespace, err)
}
Expand All @@ -261,8 +261,8 @@ func RolebindingsReferenceServiceAccount(ctx context.Context, rbNamespace, saNam
for _, rb := range rbList.Items {
for _, s := range rb.Subjects {
if s.Kind == "ServiceAccount" && s.Name == saName && s.Namespace == saNamespace {
if rec := RecorderFromContext(ctx); rec != nil {
subjectsOut, _ := k8sClient(ctx, "get", "rolebinding", rb.Metadata.Name, "-n", rbNamespace,
if rec := CommandRecorderFromContext(ctx); rec != nil {
subjectsOut, _ := K8sClient(ctx, "get", "rolebinding", rb.Metadata.Name, "-n", rbNamespace,
"-o", "jsonpath={.subjects}")
rec.RecordCustom(
fmt.Sprintf("kubectl get rolebinding %s -n %s -o jsonpath='{.subjects}' | jq .", rb.Metadata.Name, rbNamespace),
Expand All @@ -277,21 +277,21 @@ func RolebindingsReferenceServiceAccount(ctx context.Context, rbNamespace, saNam
}

func PodHasContainerCount(ctx context.Context, podName string, expected int) error {
sc := scenarioCtx(ctx)
sc := Scenario(ctx)
var actual int
waitFor(ctx, func() bool {
out, err := k8sClient(ctx, "get", "pod", podName, "-n", sc.namespace, "-o", "jsonpath={.spec.containers[*].name}")
WaitFor(ctx, func() bool {
out, err := K8sClient(ctx, "get", "pod", podName, "-n", sc.Namespace, "-o", "jsonpath={.spec.containers[*].name}")
if err != nil {
return false
}
names := strings.Fields(strings.TrimSpace(out))
actual = len(names)
return actual == expected
})
if rec := RecorderFromContext(ctx); rec != nil {
out, _ := k8sClient(ctx, "get", "pod", podName, "-n", sc.namespace, "-o", "jsonpath={.spec.containers[*].name}")
if rec := CommandRecorderFromContext(ctx); rec != nil {
out, _ := K8sClient(ctx, "get", "pod", podName, "-n", sc.Namespace, "-o", "jsonpath={.spec.containers[*].name}")
rec.RecordCustom(
fmt.Sprintf("kubectl get pod %s -n %s -o jsonpath='{.spec.containers[*].name}'", podName, sc.namespace),
fmt.Sprintf("kubectl get pod %s -n %s -o jsonpath='{.spec.containers[*].name}'", podName, sc.Namespace),
out+"\n", "",
)
}
Expand Down
Loading
Loading