Skip to content

Commit 3396c7a

Browse files
authored
Basic E2E tests (#116)
1 parent 71d4820 commit 3396c7a

File tree

13 files changed

+860
-10
lines changed

13 files changed

+860
-10
lines changed

.github/workflows/e2e.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: E2E Tests
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v5
16+
17+
- uses: actions/setup-go@v6
18+
with:
19+
go-version-file: 'go.mod'
20+
21+
- name: Run tests
22+
run: make test-e2e

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM docker.io/library/alpine:3.22 as runtime
1+
FROM docker.io/library/alpine:3.22
22

33
RUN \
44
apk add --update --no-cache \

Makefile

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ MAKEFLAGS += --no-builtin-variables
88
.SECONDARY:
99
.DEFAULT_GOAL := help
1010

11-
PROJECT_ROOT_DIR = .
12-
1311
export GOEXPERIMENT = jsonv2
1412

1513
JSONNET_FILES ?= $(shell find . -type f -not -path './vendor/*' \( -name '*.*jsonnet' -or -name '*.libsonnet' \))
@@ -87,6 +85,29 @@ LOCALBIN ?= $(shell pwd)/bin
8785
$(LOCALBIN):
8886
mkdir -p $(LOCALBIN)
8987

88+
# CertManager is installed by default; skip with:
89+
# - CERT_MANAGER_INSTALL_SKIP=true
90+
KIND_CLUSTER ?= espejote-test-e2e
91+
92+
.PHONY: setup-test-e2e
93+
setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
94+
@case "$$($(KIND) get clusters)" in \
95+
*"$(KIND_CLUSTER)"*) \
96+
echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \
97+
*) \
98+
echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \
99+
$(KIND) create cluster --name $(KIND_CLUSTER) ;; \
100+
esac
101+
102+
.PHONY: test-e2e
103+
test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
104+
KIND="$(KIND)" KIND_CLUSTER=$(KIND_CLUSTER) go test -v -tags=e2e ./test/e2e/...
105+
$(MAKE) cleanup-test-e2e
106+
107+
.PHONY: cleanup-test-e2e
108+
cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests
109+
@$(KIND) delete cluster --name $(KIND_CLUSTER)
110+
90111
###
91112
### Assets
92113
###

Makefile.vars.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
IMG_TAG ?= latest
22

33
CURDIR ?= $(shell pwd)
4-
BIN_FILENAME ?= $(CURDIR)/$(PROJECT_ROOT_DIR)/espejote
4+
BIN_FILENAME ?= espejote
55

66
KUSTOMIZE ?= go tool sigs.k8s.io/kustomize/kustomize/v5
7+
KIND ?= go tool sigs.k8s.io/kind
78

89
# Image URL to use all building/pushing image targets
910
GHCR_IMG ?= ghcr.io/vshn/espejote:$(IMG_TAG)

go.mod

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/fatih/color v1.18.0
1111
github.com/go-logr/logr v1.4.3
1212
github.com/google/go-jsonnet v0.21.0
13+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
1314
github.com/prometheus/client_golang v1.23.2
1415
github.com/prometheus/client_model v0.6.2
1516
github.com/prometheus/common v0.67.2
@@ -22,13 +23,18 @@ require (
2223
k8s.io/client-go v0.34.1
2324
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
2425
sigs.k8s.io/controller-runtime v0.22.4
26+
sigs.k8s.io/kustomize/api v0.20.1
27+
sigs.k8s.io/kustomize/kyaml v0.20.1
2528
sigs.k8s.io/yaml v1.6.0
2629
)
2730

2831
require (
32+
al.essio.dev/pkg/shellescape v1.5.1 // indirect
2933
cel.dev/expr v0.24.0 // indirect
34+
github.com/BurntSushi/toml v1.4.0 // indirect
3035
github.com/Masterminds/goutils v1.1.1 // indirect
3136
github.com/Masterminds/semver v1.5.0 // indirect
37+
github.com/Masterminds/semver/v3 v3.4.0 // indirect
3238
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
3339
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
3440
github.com/beorn7/perks v1.0.1 // indirect
@@ -69,6 +75,7 @@ require (
6975
github.com/google/cel-go v0.26.0 // indirect
7076
github.com/google/gnostic-models v0.7.0 // indirect
7177
github.com/google/go-cmp v0.7.0 // indirect
78+
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
7279
github.com/google/uuid v1.6.0 // indirect
7380
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
7481
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -90,6 +97,9 @@ require (
9097
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
9198
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
9299
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
100+
github.com/onsi/ginkgo/v2 v2.25.1 // indirect
101+
github.com/pelletier/go-toml v1.9.5 // indirect
102+
github.com/pkg/errors v0.9.1 // indirect
93103
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
94104
github.com/posener/complete v1.2.3 // indirect
95105
github.com/prometheus/procfs v0.17.0 // indirect
@@ -142,10 +152,9 @@ require (
142152
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664 // indirect
143153
sigs.k8s.io/controller-tools v0.19.0 // indirect
144154
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
145-
sigs.k8s.io/kustomize/api v0.20.1 // indirect
155+
sigs.k8s.io/kind v0.30.0 // indirect
146156
sigs.k8s.io/kustomize/cmd/config v0.20.1 // indirect
147157
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 // indirect
148-
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
149158
sigs.k8s.io/randfill v1.0.0 // indirect
150159
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
151160
)
@@ -157,5 +166,6 @@ tool (
157166
golang.org/x/tools/cmd/stringer
158167
sigs.k8s.io/controller-runtime/tools/setup-envtest
159168
sigs.k8s.io/controller-tools/cmd/controller-gen
169+
sigs.k8s.io/kind
160170
sigs.k8s.io/kustomize/kustomize/v5
161171
)

go.sum

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
2+
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
13
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
24
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
5+
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
6+
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
37
github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW515g=
48
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
59
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
610
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
711
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
812
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
13+
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
14+
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
915
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
1016
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
1117
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
@@ -108,8 +114,10 @@ github.com/google/go-jsonnet v0.21.0/go.mod h1:tCGAu8cpUpEZcdGMmdOu37nh8bGgqubhI
108114
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
109115
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
110116
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
111-
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
112-
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
117+
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
118+
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
119+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
120+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
113121
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
114122
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
115123
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
@@ -171,10 +179,12 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
171179
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
172180
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
173181
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
174-
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
175-
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
182+
github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY=
183+
github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk=
176184
github.com/onsi/gomega v1.38.1 h1:FaLA8GlcpXDwsb7m0h2A9ew2aTk3vnZMlzFgg5tz/pk=
177185
github.com/onsi/gomega v1.38.1/go.mod h1:LfcV8wZLvwcYRwPiJysphKAEsmcFnLMK/9c+PjvlX8g=
186+
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
187+
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
178188
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
179189
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
180190
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -245,6 +255,8 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx
245255
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
246256
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
247257
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
258+
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
259+
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
248260
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
249261
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
250262
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -369,6 +381,8 @@ sigs.k8s.io/controller-tools v0.19.0 h1:OU7jrPPiZusryu6YK0jYSjPqg8Vhf8cAzluP9XGI
369381
sigs.k8s.io/controller-tools v0.19.0/go.mod h1:y5HY/iNDFkmFla2CfQoVb2AQXMsBk4ad84iR1PLANB0=
370382
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
371383
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
384+
sigs.k8s.io/kind v0.30.0 h1:2Xi1KFEfSMm0XDcvKnUt15ZfgRPCT0OnCBbpgh8DztY=
385+
sigs.k8s.io/kind v0.30.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8=
372386
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
373387
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
374388
sigs.k8s.io/kustomize/cmd/config v0.20.1 h1:4APUORmZe2BYrsqgGfEKdd/r7gM6i43egLrUzilpiFo=

test/e2e/admission_e2e_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
package e2e
5+
6+
import (
7+
"bytes"
8+
"encoding/json"
9+
"os/exec"
10+
"path"
11+
"strings"
12+
"time"
13+
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
corev1 "k8s.io/api/core/v1"
17+
rbacv1 "k8s.io/api/rbac/v1"
18+
)
19+
20+
func (suite *E2ESuite) TestAdmission() {
21+
admNamespace := tmpNamespace(suite.T())
22+
suite.T().Log("Creating managed resource...")
23+
requireRun(suite.T(),
24+
exec.Command(
25+
"kubectl",
26+
"apply",
27+
"-n", admNamespace,
28+
"-f", path.Join(suite.projectDir, "test", "e2e", "testdata", "admission", "basic.yaml"),
29+
),
30+
)
31+
32+
suite.T().Log("Waiting for webhook to become ready...")
33+
suite.Require().EventuallyWithT(func(t *assert.CollectT) {
34+
cmd := exec.Command("kubectl", "create", "-f-", "-n", admNamespace, "-o=json")
35+
out := new(bytes.Buffer)
36+
cmd.Stdout = out
37+
cmd.Stdin = strings.NewReader(`
38+
apiVersion: v1
39+
kind: ConfigMap
40+
metadata:
41+
generateName: wait-webhook-
42+
`)
43+
require.NoError(t, cmd.Run())
44+
var createdCM corev1.ConfigMap
45+
require.NoError(t, json.Unmarshal(out.Bytes(), &createdCM))
46+
require.NotEmpty(t, createdCM.ObjectMeta.Annotations)
47+
require.NotEmpty(t, createdCM.ObjectMeta.Annotations["creator"])
48+
}, 1*time.Minute, 100*time.Microsecond, "Waiting for webhook to become ready and installed")
49+
50+
suite.T().Log("Creating subject cm...")
51+
requireRun(suite.T(),
52+
exec.Command(
53+
"kubectl",
54+
"create",
55+
"-n", admNamespace,
56+
"configmap",
57+
"my-cm",
58+
"--from-literal=chuck=testa",
59+
),
60+
)
61+
suite.T().Log("Verifying annotation rejection...")
62+
cmd := exec.Command(
63+
"kubectl",
64+
"annotate",
65+
"-n", admNamespace,
66+
"configmap",
67+
"my-cm",
68+
"creator=someone-else",
69+
"--overwrite",
70+
)
71+
suite.T().Log("Running command:", cmd.String())
72+
output, err := cmd.CombinedOutput()
73+
if suite.Error(err) {
74+
suite.Contains(string(output), "creator annotation is immutable")
75+
}
76+
}
77+
78+
func (suite *E2ESuite) TestClusterAdmission() {
79+
admNamespace := tmpNamespace(suite.T())
80+
suite.T().Log("Creating managed resource...")
81+
requireRun(suite.T(),
82+
exec.Command(
83+
"kubectl",
84+
"apply",
85+
"-n", admNamespace,
86+
"-f", path.Join(suite.projectDir, "test", "e2e", "testdata", "admission", "cluster.yaml"),
87+
),
88+
)
89+
90+
var crName string
91+
suite.T().Log("Waiting for webhook to become ready...")
92+
suite.Require().EventuallyWithT(func(t *assert.CollectT) {
93+
cmd := exec.Command("kubectl", "create", "-f-", "-n", admNamespace, "-o=json")
94+
out := new(bytes.Buffer)
95+
cmd.Stdout = out
96+
cmd.Stdin = strings.NewReader(`
97+
apiVersion: rbac.authorization.k8s.io/v1
98+
kind: ClusterRole
99+
metadata:
100+
generateName: wait-webhook-
101+
`)
102+
require.NoError(t, cmd.Run())
103+
var createdCR rbacv1.ClusterRole
104+
require.NoError(t, json.Unmarshal(out.Bytes(), &createdCR))
105+
require.NotEmpty(t, createdCR.ObjectMeta.Annotations)
106+
require.NotEmpty(t, createdCR.ObjectMeta.Annotations["creator"])
107+
crName = createdCR.ObjectMeta.Name
108+
}, 1*time.Minute, 100*time.Microsecond, "Waiting for webhook to become ready and installed")
109+
110+
suite.T().Log("Verifying annotation rejection...")
111+
cmd := exec.Command(
112+
"kubectl",
113+
"annotate",
114+
"clusterrole",
115+
crName,
116+
"creator=someone-else",
117+
"--overwrite",
118+
)
119+
suite.T().Log("Running command:", cmd.String())
120+
output, err := cmd.CombinedOutput()
121+
if suite.Error(err) {
122+
suite.Contains(string(output), "creator annotation is immutable")
123+
}
124+
}

0 commit comments

Comments
 (0)