Skip to content
Open
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
14 changes: 11 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ XPKG_REG_ORGS_NO_PROMOTE ?= xpkg.upbound.io/crossplane
XPKGS = provider-template
-include build/makelib/xpkg.mk

# ====================================================================================
# Setup Uptest

CROSSPLANE_VERSION ?= 2.2.0
-include build/makelib/local.xpkg.mk
-include build/makelib/controlplane.mk

UPTEST_LOCAL_DEPLOY_TARGET = local.xpkg.deploy.provider.$(PROJECT_NAME)
UPTEST_INPUT_MANIFESTS = test/e2e/00-lifecycle.yaml
-include build/makelib/uptest.mk

# NOTE(hasheddan): we force image building to happen prior to xpkg build so that
# we ensure image is present in daemon.
xpkg.build.provider-template: do.build.images
Expand All @@ -52,9 +63,6 @@ fallthrough: submodules
@echo Initial setup complete. Running make again . . .
@make

# integration tests
e2e.run: test-integration

# Run integration tests.
test-integration: $(KIND) $(KUBECTL) $(CROSSPLANE_CLI) $(HELM3)
@$(INFO) running integration tests using kind $(KIND_VERSION)
Expand Down
2 changes: 1 addition & 1 deletion build
16 changes: 16 additions & 0 deletions test/e2e/00-lifecycle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: sample.template.crossplane.io/v1alpha1
kind: MyType
metadata:
name: e2e-lifecycle-test
namespace: default
annotations:
uptest.upbound.io/timeout: "120"
uptest.upbound.io/conditions: "Ready"
uptest.upbound.io/post-assert-hook: "hooks/post-assert-lifecycle.sh"
uptest.upbound.io/pre-delete-hook: "hooks/pre-delete-lifecycle.sh"
spec:
forProvider:
configurableField: "initial-value"
providerConfigRef:
name: example
kind: ProviderConfig
65 changes: 65 additions & 0 deletions test/e2e/hooks/post-assert-lifecycle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail

RESOURCE_NAME="e2e-lifecycle-test"
NAMESPACE="default"

# Verify status and external-name are set
CONFIGURED=$(${KUBECTL} get mytype "${RESOURCE_NAME}" -n "${NAMESPACE}" \
-o jsonpath='{.status.atProvider.configurableField}')

if [[ -z "${CONFIGURED}" ]]; then
echo "FAIL: status.atProvider.configurableField is empty"
exit 1
fi
echo "PASS: status.atProvider.configurableField = ${CONFIGURED}"

EXTERNAL_NAME=$(${KUBECTL} get mytype "${RESOURCE_NAME}" -n "${NAMESPACE}" \
-o jsonpath='{.metadata.annotations.crossplane\.io/external-name}')

if [[ -z "${EXTERNAL_NAME}" ]]; then
echo "FAIL: external-name annotation is not set"
exit 1
fi
echo "PASS: external-name = ${EXTERNAL_NAME}"

# ---- Error case: MyType with non-existent ProviderConfig ----
echo ""
echo "Testing error case: MyType with missing ProviderConfig..."

ERROR_RESOURCE="e2e-error-no-config"

# Ensure cleanup on any exit, preserving the original exit code
trap 'rc=$?; ${KUBECTL} delete mytype "${ERROR_RESOURCE}" -n "${NAMESPACE}" --ignore-not-found || true; exit $rc' EXIT

cat <<EOF | ${KUBECTL} apply -f -
apiVersion: sample.template.crossplane.io/v1alpha1
kind: MyType
metadata:
name: ${ERROR_RESOURCE}
namespace: ${NAMESPACE}
spec:
forProvider:
configurableField: "test"
providerConfigRef:
name: nonexistent-config
kind: ProviderConfig
EOF

# Wait for Synced=False instead of a fixed sleep
echo "Waiting for Synced=False on error resource..."
${KUBECTL} wait mytype "${ERROR_RESOURCE}" -n "${NAMESPACE}" \
--for=jsonpath='{.status.conditions[?(@.type=="Synced")].status}'=False \
--timeout=60s

echo "PASS: Synced=False as expected"

# Verify the error message references the missing ProviderConfig
MESSAGE=$(${KUBECTL} get mytype "${ERROR_RESOURCE}" -n "${NAMESPACE}" \
-o jsonpath='{.status.conditions[?(@.type=="Synced")].message}')

if [[ "${MESSAGE}" != *"nonexistent-config"* ]]; then
echo "FAIL: error message does not reference missing ProviderConfig: ${MESSAGE}"
exit 1
fi
echo "PASS: error message indicates config issue: ${MESSAGE}"
25 changes: 25 additions & 0 deletions test/e2e/hooks/pre-delete-lifecycle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail

RESOURCE_NAME="e2e-lifecycle-test"
NAMESPACE="default"

# Fetch all needed fields
READY=$(${KUBECTL} get mytype "${RESOURCE_NAME}" -n "${NAMESPACE}" \
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
SPEC_VALUE=$(${KUBECTL} get mytype "${RESOURCE_NAME}" -n "${NAMESPACE}" \
-o jsonpath='{.spec.forProvider.configurableField}')
STATUS_VALUE=$(${KUBECTL} get mytype "${RESOURCE_NAME}" -n "${NAMESPACE}" \
-o jsonpath='{.status.atProvider.configurableField}')

if [[ "${READY}" != "True" ]]; then
echo "FAIL: resource is not Ready before deletion (status: ${READY})"
exit 1
fi
echo "PASS: resource is Ready before deletion"

if [[ "${SPEC_VALUE}" != "${STATUS_VALUE}" ]]; then
echo "FAIL: spec (${SPEC_VALUE}) != status (${STATUS_VALUE}) - update not propagated"
exit 1
fi
echo "PASS: spec and status are in sync (${SPEC_VALUE})"
62 changes: 62 additions & 0 deletions test/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail

echo "Granting provider CRD access for safe-start capability..."

SA_NAME=""
for i in $(seq 1 60); do
SA_NAME=$(${KUBECTL} get sa -n crossplane-system -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n' | grep '^provider-template-' | head -1 || true)
if [[ -n "${SA_NAME}" ]]; then
break
fi
echo "Waiting for provider service account to be created... (attempt ${i}/60)"
sleep 2
done

if [[ -z "${SA_NAME}" ]]; then
echo "FAIL: could not find provider-template service account after 120s"
exit 1
fi
echo "Found provider service account: ${SA_NAME}"

# Required by safe-start gate: provider needs CRD list/watch access
cat <<EOF | ${KUBECTL} apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: provider-template-crd-access
labels:
pkg.crossplane.io/provider: provider-template
rules:
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: provider-template-crd-access
labels:
pkg.crossplane.io/provider: provider-template
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: provider-template-crd-access
subjects:
- kind: ServiceAccount
name: ${SA_NAME}
namespace: crossplane-system
EOF

echo "Waiting for provider to become healthy..."
${KUBECTL} wait provider.pkg provider-template \
--for=condition=Healthy \
--timeout=180s

# Resolve project root relative to this script's location
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"

echo "Creating ProviderConfig and credentials from examples..."
${KUBECTL} apply -f "${PROJECT_ROOT}/examples/provider/config.yaml"

echo "Setup complete."
Loading