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
179 changes: 179 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Team Operator Integration Tests
#
# This workflow runs integration tests using both:
# - envtest (API-level tests, fast)
# - kind cluster (full stack tests, slower)
#
# Envtest runs on every PR. Kind tests run on every PR that touches
# relevant paths (api/, internal/, Dockerfile, etc.), on push to main,
# nightly, and on manual dispatch.

name: Integration Tests

on:
push:
branches:
- main
paths-ignore:
- '**/*.md'
- 'docs/**'
- '.claude/**'
- 'LICENSE'
pull_request:
paths-ignore:
- '**/*.md'
- 'docs/**'
- '.claude/**'
- 'LICENSE'
schedule:
# Run nightly at 2:00 AM UTC
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
run_kind_tests:
description: 'Run kind integration tests'
required: false
default: 'true'
type: boolean

permissions:
contents: read

env:
KIND_VERSION: 'v0.23.0'
KIND_CLUSTER_NAME: 'team-operator-test'

jobs:
check-changes:
name: Detect relevant changes
runs-on: ubuntu-latest
outputs:
relevant: ${{ steps.filter.outputs.relevant }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
relevant:
- 'api/**'
- 'internal/**'
- 'cmd/**'
- 'hack/test-kind.sh'
- 'dist/chart/**'
- 'Dockerfile'
- 'Makefile'
- 'go.mod'
- 'go.sum'

envtest:
name: Envtest (API-level tests)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
cache-dependency-path: go.sum

- name: Cache envtest binaries
uses: actions/cache@v4
with:
path: bin/
key: ${{ runner.os }}-envtest-${{ hashFiles('Makefile') }}
restore-keys: |
${{ runner.os }}-envtest-

- name: Install envtest
run: make envtest

- name: Run envtest suite
run: make go-test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload coverage
uses: codecov/codecov-action@v5
with:
files: coverage.out
flags: envtest
fail_ci_if_error: false

kind:
name: Kind (full stack tests)
runs-on: ubuntu-latest
needs: [envtest, check-changes]
# Run on PRs touching relevant paths, push to main, nightly, or manual trigger
if: |
(github.event_name == 'pull_request' && needs.check-changes.outputs.relevant == 'true') ||
github.ref == 'refs/heads/main' ||
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_kind_tests == 'true')
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
cache-dependency-path: go.sum

- name: Install kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

- name: Install Helm
uses: azure/setup-helm@v4
with:
version: 'latest'

- name: Create kind cluster
run: make kind-create KIND_CLUSTER_NAME=${{ env.KIND_CLUSTER_NAME }}

- name: Run integration tests
run: |
make test-kind KIND_CLUSTER_NAME=${{ env.KIND_CLUSTER_NAME }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Collect cluster logs on failure
if: failure()
run: |
echo "=== Cluster Info ==="
kubectl cluster-info --context kind-${{ env.KIND_CLUSTER_NAME }}
echo ""
echo "=== All Pods ==="
kubectl get pods -A
echo ""
echo "=== Events ==="
kubectl get events -A --sort-by='.lastTimestamp'
echo ""
echo "=== Operator Logs ==="
kubectl logs -n posit-team-system -l app.kubernetes.io/name=team-operator --tail=100 || true

- name: Delete kind cluster
if: always()
run: make kind-delete KIND_CLUSTER_NAME=${{ env.KIND_CLUSTER_NAME }}

# Summary job for branch protection
integration-tests-complete:
name: Integration Tests Complete
runs-on: ubuntu-latest
needs: [envtest]
if: always()
steps:
- name: Check results
run: |
if [[ "${{ needs.envtest.result }}" != "success" ]]; then
echo "Envtest failed"
exit 1
fi
echo "All required tests passed!"
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG GO_VERSION=1.24
ARG GO_VERSION=1.25
# Build the team-operator binary
FROM golang:${GO_VERSION} AS builder
ARG TARGETOS
Expand Down
54 changes: 53 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,71 @@ test: manifests generate-all fmt vet go-test cov ## Run generation and test comm
.PHONY: go-test
go-test: envtest ## Run only the go tests.
KUBEBUILDER_ASSETS="$(shell "$(ENVTEST)" use "$(ENVTEST_K8S_VERSION)" --bin-dir "$(LOCALBIN)" -p path)" \
go test -v ./... -race -covermode=atomic -coverprofile coverage.out
sh -c 'go test -buildvcs=false -v $$(go list -buildvcs=false -f '\''{{if or .TestGoFiles .XTestGoFiles}}{{.ImportPath}}{{end}}'\'' ./...) -race -covermode=atomic -coverprofile coverage.out'

.PHONY: cov
cov: ## Show the coverage report at the function level.
$(SED) -i '/team-operator\/client-go/d' coverage.out
go tool cover -func coverage.out

##@ Integration Testing

KIND_CLUSTER_NAME ?= team-operator-test

.PHONY: kind-create
kind-create: ## Create a kind cluster for integration testing.
@if kind get clusters | grep -q "^$(KIND_CLUSTER_NAME)$$"; then \
echo "Kind cluster '$(KIND_CLUSTER_NAME)' already exists"; \
else \
echo "Creating kind cluster '$(KIND_CLUSTER_NAME)'..."; \
kind create cluster --name $(KIND_CLUSTER_NAME) --wait 60s; \
fi

.PHONY: kind-delete
kind-delete: ## Delete the kind cluster.
kind delete cluster --name $(KIND_CLUSTER_NAME) || true

.PHONY: kind-load-image
kind-load-image: docker-build ## Load the operator image into kind cluster.
kind load docker-image $(IMG) --name $(KIND_CLUSTER_NAME)

.PHONY: kind-setup
kind-setup: kind-create docker-build helm-generate ## Set up kind cluster and deploy operator (run once, or after code changes to reload).
@echo "Setting up kind cluster '$(KIND_CLUSTER_NAME)'..."
./hack/test-kind.sh $(KIND_CLUSTER_NAME) setup

.PHONY: kind-test
kind-test: ## Run integration tests against an existing kind cluster (requires kind-setup first).
./hack/test-kind.sh $(KIND_CLUSTER_NAME) test

.PHONY: kind-teardown
kind-teardown: ## Tear down the kind cluster and remove all test resources.
./hack/test-kind.sh $(KIND_CLUSTER_NAME) teardown
kind delete cluster --name $(KIND_CLUSTER_NAME) || true

.PHONY: test-kind
test-kind: kind-create docker-build helm-generate ## Build operator image and run integration tests on a kind cluster.
@echo "Running integration tests on kind cluster '$(KIND_CLUSTER_NAME)'..."
./hack/test-kind.sh $(KIND_CLUSTER_NAME)

.PHONY: test-kind-full
test-kind-full: kind-delete kind-create test-kind ## Run full integration tests (clean cluster).
@echo "Full integration test completed."

.PHONY: test-integration
test-integration: go-test test-kind ## Run all tests (unit + integration).
@echo "All tests completed."

##@ Build

.PHONY: build
build: manifests generate-all fmt vet ## Build manager binary.
go build -o bin/team-operator ./cmd/team-operator/main.go

.PHONY: docker-build
docker-build: build ## Build the operator Docker image.
docker build -t $(IMG) .

.PHONY: distclean
distclean:
git clean -xd bin/ || true
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,41 @@ just helm-install # Install via Helm
just helm-uninstall # Uninstall via Helm
```

### Testing

**Unit tests** (fast, no cluster required):

```bash
make go-test
```

**Integration tests** — two workflows:

*One-shot (CI-style):* creates a cluster, runs all tests, and tears everything down.

```bash
make test-kind # create → deploy → test → destroy
make test-kind-full # same, but forces a clean cluster first
```

*Dev loop (recommended for iterative development):* keep the cluster running between test runs.

```bash
# One-time setup: create cluster and deploy operator
make kind-setup

# After making code changes, reload the image and re-deploy
make kind-setup

# Run tests against the running cluster
make kind-test

# When done for the day
make kind-teardown
```

See [docs/testing.md](docs/testing.md) for full details.

## Configuration

The Site CR defines a complete Posit Team deployment. Secrets and licenses are managed automatically through cloud provider integration (AWS Secrets Manager or Azure Key Vault) - configured during PTD bootstrap.
Expand Down
Loading