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
2 changes: 1 addition & 1 deletion boilerplate/_data/backing-image-tag
Original file line number Diff line number Diff line change
@@ -1 +1 @@
image-v8.4.3
latest
2 changes: 1 addition & 1 deletion boilerplate/_data/last-boilerplate-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
195c29d18279f69ea5b4e53ae9d90a795c27144a
1191e12968520575f7177f1ff055e966b91cd52a
4 changes: 2 additions & 2 deletions boilerplate/_lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ current_branch() {
)
}

## image_exits_in_repo IMAGE_URI
## image_exists_in_repo IMAGE_URI
#
# Checks whether IMAGE_URI -- e.g. quay.io/app-sre/osd-metrics-exporter:abcd123
# -- exists in the remote repository.
Expand Down Expand Up @@ -190,7 +190,7 @@ IMAGE_NAMESPACE=openshift
IMAGE_NAME=boilerplate
# LATEST_IMAGE_TAG may be set manually or by `update`, in which case
# that's the value we want to use.
if [[ -z "$LATEST_IMAGE_TAG" ]]; then
if [[ -z "$LATEST_IMAGE_TAG" && -z "$SKIP_LATEST_IMAGE_TAG_RESOLUTION" ]]; then
# (Non-ancient) consumers will have the tag in this file.
if [[ -f ${CONVENTION_ROOT}/_data/backing-image-tag ]]; then
LATEST_IMAGE_TAG=$(cat ${CONVENTION_ROOT}/_data/backing-image-tag)
Expand Down
9 changes: 9 additions & 0 deletions boilerplate/openshift/golang-osd-e2e/e2e-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ objects:
secret:
secretName: osde2e-gcp-credentials
optional: true
- name: test-run-results
emptyDir: {}
- name: tmp
emptyDir: {}
containers:
- name: osde2e
image: quay.io/redhat-services-prod/osde2e-cicada-tenant/osde2e:latest
Expand Down Expand Up @@ -80,6 +84,10 @@ objects:
- name: osde2e-gcp-sc
readOnly: true
mountPath: "/etc/osde2e-gcp-sc"
- name: test-run-results
mountPath: /test-run-results
- name: tmp
mountPath: /tmp
resources:
requests:
cpu: "300m"
Expand All @@ -89,6 +97,7 @@ objects:
memory: "1200Mi"
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: [ "ALL" ]
Expand Down
194 changes: 194 additions & 0 deletions boilerplate/openshift/golang-osd-e2e/e2e_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// THIS FILE IS GENERATED BY BOILERPLATE. DO NOT EDIT.
//go:build osde2e
// +build osde2e

package osde2etests

import (
"context"
"fmt"

"github.com/go-logr/logr"
openshiftapi "github.com/openshift/api"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// E2EClientOption configures an E2EClient during construction.
// Use WithScheme to register operator-specific CRD types.
type E2EClientOption func(*e2eClientConfig) error

// e2eClientConfig holds options accumulated before client creation.
type e2eClientConfig struct {
extraSchemes []func(*runtime.Scheme) error
}

// WithScheme returns an option that registers additional types with the
// client's runtime.Scheme. Each operator calls this to add its own CRD
// types without editing this boilerplate file.
//
// Example:
//
// c, err := NewE2EClient(log, WithScheme(myoperatorv1.AddToScheme))
func WithScheme(addToScheme func(*runtime.Scheme) error) E2EClientOption {
return func(cfg *e2eClientConfig) error {
cfg.extraSchemes = append(cfg.extraSchemes, addToScheme)
return nil
}
}

// E2EClient wraps controller-runtime's client.Client with convenience
// methods for OSD operator e2e tests. It replaces the osde2e-common
// dependency entirely.
type E2EClient struct {
client.Client
config *rest.Config
log logr.Logger
extraSchemes []func(*runtime.Scheme) error
}

// NewE2EClient creates an E2EClient by loading kubeconfig from the
// KUBECONFIG env var (falling back to ~/.kube/config). Pass WithScheme
// options to register operator-specific CRD types.
func NewE2EClient(log logr.Logger, opts ...E2EClientOption) (*E2EClient, error) {
cfg, err := loadKubeConfig()
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig: %w", err)
}
return NewE2EClientFromConfig(cfg, log, opts...)
}

// NewE2EClientFromConfig creates an E2EClient from an explicit
// rest.Config. The base scheme always includes core/v1, apps/v1, and
// the OpenShift API types. Pass WithScheme options to register
// additional types (e.g. operator CRDs).
func NewE2EClientFromConfig(cfg *rest.Config, log logr.Logger, opts ...E2EClientOption) (*E2EClient, error) {
var ecfg e2eClientConfig
for _, opt := range opts {
if err := opt(&ecfg); err != nil {
return nil, fmt.Errorf("failed to apply client option: %w", err)
}
}

scheme := runtime.NewScheme()

// Register base types that every OSD operator test needs.
if err := corev1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("failed to register core/v1: %w", err)
}
if err := appsv1.AddToScheme(scheme); err != nil {
return nil, fmt.Errorf("failed to register apps/v1: %w", err)
}
if err := openshiftapi.Install(scheme); err != nil {
return nil, fmt.Errorf("failed to register openshift api: %w", err)
}

// Register operator-specific types supplied via WithScheme.
for _, addToScheme := range ecfg.extraSchemes {
if err := addToScheme(scheme); err != nil {
return nil, fmt.Errorf("failed to register extra scheme: %w", err)
}
}

c, err := client.New(cfg, client.Options{Scheme: scheme})
if err != nil {
return nil, fmt.Errorf("failed to create controller-runtime client: %w", err)
}
return &E2EClient{Client: c, config: cfg, log: log, extraSchemes: ecfg.extraSchemes}, nil
}

// Get wraps client.Get with positional name/namespace args for API
// compatibility with the old osde2e-common openshift.Client.
func (c *E2EClient) Get(ctx context.Context, name, namespace string, obj client.Object) error {
return c.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, obj)
}

// GetScheme returns the client's runtime.Scheme.
func (c *E2EClient) GetScheme() *runtime.Scheme {
return c.Client.Scheme()
}

// GetConfig returns the rest.Config used by this client.
func (c *E2EClient) GetConfig() *rest.Config {
return c.config
}

// Impersonate returns a new E2EClient that acts as the given user and
// groups. The "system:authenticated" and "system:authenticated:oauth"
// groups are added automatically when user is non-empty.
func (c *E2EClient) Impersonate(user string, groups ...string) (*E2EClient, error) {
if user != "" {
groups = append(groups, "system:authenticated", "system:authenticated:oauth")
}
impersonatedCfg := rest.CopyConfig(c.config)
impersonatedCfg.Impersonate = rest.ImpersonationConfig{UserName: user, Groups: groups}

// Preserve operator-specific schemes so the impersonated client
// can still work with custom CRD types.
opts := make([]E2EClientOption, len(c.extraSchemes))
for i, s := range c.extraSchemes {
opts[i] = WithScheme(s)
}
return NewE2EClientFromConfig(impersonatedCfg, c.log, opts...)
}

const (
metadataConfigMap = "osd-cluster-metadata"
configNamespace = "openshift-config"
)

// getClusterMetadata reads the osd-cluster-metadata configmap from the
// openshift-config namespace.
func (c *E2EClient) getClusterMetadata(ctx context.Context) (map[string]string, error) {
var cm corev1.ConfigMap
if err := c.Get(ctx, metadataConfigMap, configNamespace, &cm); err != nil {
return nil, err
}
return cm.Data, nil
}

// IsSTS returns true if the cluster is configured with STS (Security
// Token Service) authentication.
func (c *E2EClient) IsSTS(ctx context.Context) (bool, error) {
data, err := c.getClusterMetadata(ctx)
if err != nil {
return false, err
}
return data["api.openshift.com_sts"] == "true", nil
}

// GetProvider returns the cloud provider name from cluster metadata
// (e.g. "aws", "gcp").
func (c *E2EClient) GetProvider(ctx context.Context) (string, error) {
data, err := c.getClusterMetadata(ctx)
if err != nil {
return "", err
}
return data["hive.openshift.io_cluster-platform"], nil
}

// GetRegion returns the cloud region from cluster metadata (e.g.
// "us-east-1").
func (c *E2EClient) GetRegion(ctx context.Context) (string, error) {
data, err := c.getClusterMetadata(ctx)
if err != nil {
return "", err
}
return data["hive.openshift.io_cluster-region"], nil
}

// loadKubeConfig loads a rest.Config using client-go's default loading
// rules. This properly handles multi-path KUBECONFIG (e.g.
// "/a/config:/b/config") and falls back to ~/.kube/config when the env
// var is unset.
func loadKubeConfig() (*rest.Config, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
return kubeConfig.ClientConfig()
}
30 changes: 20 additions & 10 deletions boilerplate/openshift/golang-osd-e2e/gangway-bridge-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ objects:
[[ "${MAX_RETRIES}" =~ ^[0-9]+$ ]] || { log "ERROR: MAX_RETRIES must be a non-negative integer"; exit 1; }
[[ "${INITIAL_DELAY}" =~ ^[0-9]+$ ]] || { log "ERROR: INITIAL_DELAY must be a non-negative integer"; exit 1; }

if [[ "${INITIAL_DELAY}" -gt 0 ]]; then
log "Waiting ${INITIAL_DELAY}s before first Gangway call (INITIAL_DELAY)..."
sleep "${INITIAL_DELAY}"
fi

# Backoff sum: base 30s doubling each retry, capped at 900s, plus 15s max jitter
MAX_BACKOFF_SUM=$(( 30 * ((1 << MAX_RETRIES) - 1) + MAX_RETRIES * 15 ))
MAX_BACKOFF_SUM=0
for (( i = 0; i < MAX_RETRIES; i++ )); do
b=$(( i < 5 ? 30 * (1 << i) : 900 ))
(( b > 900 )) && b=900
MAX_BACKOFF_SUM=$(( MAX_BACKOFF_SUM + b + 15 ))
done
# Each attempt may overshoot TIMEOUT by up to max(POLL_INTERVAL, 300s max backoff) +
# status-request max-time (30s) on the last poll cycle
POLL_OVERSHOOT=$(( (POLL_INTERVAL > 300 ? POLL_INTERVAL : 300) + 30 ))
Expand All @@ -77,9 +77,14 @@ objects:
exit 1
fi

if [[ "${INITIAL_DELAY}" -gt 0 ]]; then
log "Waiting ${INITIAL_DELAY}s before first Gangway call (INITIAL_DELAY)..."
sleep "${INITIAL_DELAY}"
fi

BODY='{"job_execution_type":"1"}'
if [[ -n "${JOB_ENVS:-}" ]]; then
ENVS=$(echo "${JOB_ENVS}" | jq -Rn '[inputs // input | split(",")[] | split("=") | {(.[0]): .[1:] | join("=")}] | add' <<< "${JOB_ENVS}")
ENVS=$(echo "${JOB_ENVS}" | jq -Rn '[inputs | split(",") | .[] | select(length > 0) | split("=") | {(.[0]): (.[1:] | join("="))}] | add // empty')
BODY=$(jq -cn --argjson e "$ENVS" '{"job_execution_type":"1","pod_spec_options":{"envs":$e}}')
fi

Expand Down Expand Up @@ -150,15 +155,20 @@ objects:
esac
done
log "Prow logs: ${PROW_URL}"
log "Timeout"; return 1
log "Timeout"; return 2
}

ATTEMPT=0
while true; do
ATTEMPT=$((ATTEMPT + 1))
log "Attempt ${ATTEMPT} of $((MAX_RETRIES + 1))"
if trigger_and_poll; then
rc=0
trigger_and_poll || rc=$?
if [[ $rc -eq 0 ]]; then
exit 0
elif [[ $rc -eq 2 ]]; then
log "Timed out waiting for Prow job — not retrying (the job may still be running)"
exit 1
fi
if [[ $ATTEMPT -gt $MAX_RETRIES ]]; then
log "All attempts exhausted"
Expand All @@ -168,7 +178,7 @@ objects:
log "Skipping backoff (already waited for Retry-After)"
RATE_LIMITED_WAITED=0
else
BACKOFF=$(( 30 * (1 << (ATTEMPT - 1)) ))
BACKOFF=$(( ATTEMPT > 6 ? 900 : 30 * (1 << (ATTEMPT - 1)) ))
[[ $BACKOFF -gt 900 ]] && BACKOFF=900
JITTER=$(( RANDOM % 16 ))
DELAY=$(( BACKOFF + JITTER ))
Expand Down
13 changes: 10 additions & 3 deletions boilerplate/openshift/golang-osd-e2e/update
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ OPERATOR_NAME_CAMEL_CASE=${OPERATOR_PROPER_NAME// /}

mkdir -p "${E2E_SUITE_DIRECTORY}"

# Copy the generalized e2e client helper (always overwritten).
echo "syncing ${E2E_SUITE_DIRECTORY}/e2e_client.go"
cp "$(dirname $0)/e2e_client.go" "${E2E_SUITE_DIRECTORY}/e2e_client.go"

E2E_SUITE_BUILDER_IMAGE=registry.ci.openshift.org/openshift/release:rhel-9-release-golang-1.26-openshift-4.22
if [[ -n ${KONFLUX_BUILDS} ]]; then
E2E_SUITE_BUILDER_IMAGE="brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_1.26"
Expand All @@ -44,11 +48,14 @@ tee "${E2E_SUITE_DIRECTORY}/Dockerfile" <<EOF
# THIS FILE IS GENERATED BY BOILERPLATE. DO NOT EDIT.
FROM ${E2E_SUITE_BUILDER_IMAGE} as builder
WORKDIR /go/src/${GO_MODULE_PATH}/
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOFLAGS="-mod=mod" go test ./test/e2e -v -c --tags=osde2e -o /e2e.test

FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
COPY --from=builder ./e2e.test e2e.test
COPY --from=builder /e2e.test /e2e.test
USER 1001
ENTRYPOINT [ "/e2e.test" ]
EOF

Expand Down Expand Up @@ -106,10 +113,10 @@ When updating your operator, add e2e tests for new functionality and ensure exis

1. Run "make e2e-binary-build" to make sure e2e tests build
2. Deploy your new version of operator in a test cluster
3. Run "go install github.com/onsi/ginkgo/ginkgo@latest"
3. Run "go install github.com/onsi/ginkgo/v2/ginkgo@latest"
4. Get kubeadmin credentials from your cluster using

ocm get /api/clusters_mgmt/v1/clusters/(cluster-id)/credentials | jq -r .kubeconfig > /(path-to)/kubeconfig
(umask 077 && ocm get /api/clusters_mgmt/v1/clusters/(cluster-id)/credentials | jq -r .kubeconfig > /(path-to)/kubeconfig)

5. Run test suite using

Expand Down
9 changes: 5 additions & 4 deletions boilerplate/update
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,11 @@ if [ ! -f "$CONFIG_FILE" ]; then
exit 1
fi

# The most recent build image tag. Export this for individual `update` scripts.
if [[ -z "$LATEST_IMAGE_TAG" ]]; then
export LATEST_IMAGE_TAG=$(cd $BP_CLONE; git describe --tags --abbrev=0 --match image-v*)
fi
# Consuming repositories use the stable `latest` tag for the boilerplate
# backing image. Keep an explicit value for local testing or pinning, but do
# not infer a tag from the consumer's old backing-image-tag file or from Git
# release tags. This also migrates existing consumers to `latest` below.
export LATEST_IMAGE_TAG="${LATEST_IMAGE_TAG:-latest}"

# The boilerplate commit hash. Export for convention `update` scripts.
export BOILERPLATE_COMMIT=$(cd ${BP_CLONE} && git rev-parse HEAD)
Expand Down
5 changes: 4 additions & 1 deletion test/e2e/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# THIS FILE IS GENERATED BY BOILERPLATE. DO NOT EDIT.
FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_1.26 as builder
WORKDIR /go/src/github.com/openshift/managed-cluster-validating-webhooks/
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOFLAGS="-mod=mod" go test ./test/e2e -v -c --tags=osde2e -o /e2e.test

FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
COPY --from=builder ./e2e.test e2e.test
COPY --from=builder /e2e.test /e2e.test
USER 1001
ENTRYPOINT [ "/e2e.test" ]
4 changes: 2 additions & 2 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ When updating your operator, add e2e tests for new functionality and ensure exis

1. Run "make e2e-binary-build" to make sure e2e tests build
2. Deploy your new version of operator in a test cluster
3. Run "go install github.com/onsi/ginkgo/ginkgo@latest"
3. Run "go install github.com/onsi/ginkgo/v2/ginkgo@latest"
4. Get kubeadmin credentials from your cluster using

ocm get /api/clusters_mgmt/v1/clusters/(cluster-id)/credentials | jq -r .kubeconfig > /(path-to)/kubeconfig
(umask 077 && ocm get /api/clusters_mgmt/v1/clusters/(cluster-id)/credentials | jq -r .kubeconfig > /(path-to)/kubeconfig)

5. Run test suite using

Expand Down
Loading