Skip to content

Commit 6618a5d

Browse files
authored
Adds integration tests (#7)
For the integration tests, we're deploying k8s and deploy the webhook present in this repository. We then tests various scenarios in which the webhook should act or not. One of the integration tests runs a rock with a readOnlyRootFilesystem security context and ensures that it starts properly with this webhook.
1 parent cd60143 commit 6618a5d

File tree

6 files changed

+258
-4
lines changed

6 files changed

+258
-4
lines changed

tests/integration/test_mutating_pebble_webhook.py

Lines changed: 194 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,208 @@
33
# See LICENSE file for licensing details
44
#
55

6+
import base64
67
import logging
8+
import os
9+
import pathlib
10+
import subprocess
711

12+
import pytest
13+
import yaml
814
from k8s_test_harness import harness
9-
from k8s_test_harness.util import env_util
15+
from k8s_test_harness.util import constants, env_util, k8s_util
1016

1117
LOG = logging.getLogger(__name__)
1218

19+
DIR = pathlib.Path(__file__).absolute().parent
20+
TEMPLATES_DIR = DIR / ".." / "templates"
21+
BASE_DIR = DIR / ".." / ".."
22+
MANIFESTS_DIR = BASE_DIR / "manifests"
1323

14-
def test_integration_mutating_pebble_webhook(function_instance: harness.Instance):
24+
PEBBLE_ENV = "PEBBLE"
25+
PEBBLE_ENV_COPY_ONCE = "PEBBLE_COPY_ONCE"
26+
27+
PEBBLE_VOLUME_NAME = "pebble-dir"
28+
PEBBLE_DEFAULT_DIR = "/var/lib/pebble/default"
29+
PEBBLE_WRITABLE_SUBPATH = "writable"
30+
PEBBLE_DEFAULT_WRITABLE_DIR = os.path.join(PEBBLE_DEFAULT_DIR, PEBBLE_WRITABLE_SUBPATH)
31+
32+
33+
@pytest.fixture(scope="module")
34+
def webhook_instance(module_instance: harness.Instance):
1535
rock = env_util.get_build_meta_info_for_rock_version(
1636
"mutating-pebble-webhook", "0.0.1", "amd64"
1737
)
1838

19-
LOG.info(f"Using rock: {rock.image}")
20-
LOG.warn("Integration tests are not yet implemented yet")
39+
# Generate certificates needed by webhook.
40+
for target in ["generate-selfsigned-cert", "set-webhook-cabundle"]:
41+
make_command = [
42+
"make",
43+
"-C",
44+
BASE_DIR,
45+
target,
46+
]
47+
subprocess.run(make_command, check=True)
48+
49+
# Some specs need to be updated before applying.
50+
server_cert = pathlib.Path(BASE_DIR / "tls" / "server.crt").read_bytes()
51+
server_key = pathlib.Path(BASE_DIR / "tls" / "server.key").read_bytes()
52+
file_updates = {
53+
"webhook-deployment.yaml": {
54+
# what_to_update: new_value.
55+
"ghcr.io/canonical/mutating-pebble-webhook": f"{rock.image} #",
56+
},
57+
"webhook-secret.yaml": {
58+
"tls.crt": f"tls.crt: {base64.b64encode(server_cert).decode()} #",
59+
"tls.key": f"tls.key: {base64.b64encode(server_key).decode()} #",
60+
},
61+
}
62+
63+
# Apply updates if needed, and deploy webhook.
64+
for filename in [
65+
"webhook-ns.yaml",
66+
"webhook-secret.yaml",
67+
"webhook-deployment.yaml",
68+
"webhook-svc.yaml",
69+
"webhook.yaml",
70+
]:
71+
spec = pathlib.Path(MANIFESTS_DIR / filename).read_text()
72+
if filename in file_updates:
73+
for old, new in file_updates[filename].items():
74+
spec = spec.replace(old, new, 1)
75+
76+
module_instance.exec(
77+
["k8s", "kubectl", "apply", "-f", "-"],
78+
input=spec.encode(),
79+
)
80+
81+
k8s_util.wait_for_deployment(
82+
module_instance, "mutating-pebble-webhook", "pebble-webhook"
83+
)
84+
85+
yield module_instance
86+
87+
88+
def _apply_pod(instance: harness.Instance, spec_filename: str):
89+
"""Applies the given Pod spec and waits for it to become Ready/
90+
91+
Returns the new Pod spec.
92+
"""
93+
# NOTE(claudiub): pod names should be unique, as we're not cleaning them up anyways.
94+
95+
spec = pathlib.Path(TEMPLATES_DIR / spec_filename).read_text()
96+
spec_yaml = yaml.safe_load(spec)
97+
pod_name = spec_yaml["metadata"]["name"]
98+
99+
instance.exec(
100+
["k8s", "kubectl", "apply", "-f", "-"],
101+
input=spec.encode(),
102+
)
103+
104+
k8s_util.wait_for_resource(
105+
instance,
106+
"pod",
107+
pod_name,
108+
condition=constants.K8S_CONDITION_READY,
109+
retry_delay_s=10,
110+
)
111+
112+
# Get the Pod yaml spec and return it.
113+
process = instance.exec(
114+
["k8s", "kubectl", "get", "-o", "yaml", "pod", pod_name],
115+
capture_output=True,
116+
)
117+
118+
return yaml.safe_load(process.stdout)
119+
120+
121+
def test_webhook_noop(webhook_instance: harness.Instance):
122+
pod = _apply_pod(webhook_instance, "pod-noop.yaml")
123+
124+
# The pod should not have any environment variables, and no "pebble-dir" volume.
125+
assert "env" not in pod["spec"]["containers"][0]
126+
127+
volume_names = [vol["name"] for vol in pod["spec"]["volumes"]]
128+
assert PEBBLE_VOLUME_NAME not in volume_names
129+
130+
131+
def test_webhook_already_has_mount(webhook_instance: harness.Instance):
132+
pod = _apply_pod(webhook_instance, "pod-has-mount.yaml")
133+
container = pod["spec"]["containers"][0]
134+
135+
# The pod already has a mount in the PEBBLE default path, and the webhook should have
136+
# skipped processing it. It should not have any environment variables, and no
137+
# "pebble-dir" volume.
138+
assert "env" not in container
139+
140+
# Sanity check, make sure we do have a mount.
141+
volume_mounts = [mount["mountPath"] for mount in container["volumeMounts"]]
142+
assert PEBBLE_DEFAULT_DIR in volume_mounts
143+
144+
volume_names = [vol["name"] for vol in pod["spec"]["volumes"]]
145+
assert PEBBLE_VOLUME_NAME not in volume_names
146+
147+
148+
def test_webhook_mixed_containers(webhook_instance: harness.Instance):
149+
pod = _apply_pod(webhook_instance, "pod-mixed.yaml")
150+
151+
# The normal container should not have any env vars set, and no volume mounts for Pebble.
152+
container = pod["spec"]["containers"][0]
153+
assert "env" not in container
154+
155+
volume_mounts = [mount["name"] for mount in container["volumeMounts"]]
156+
assert PEBBLE_VOLUME_NAME not in volume_mounts
157+
158+
# The read-only container should have the PEBBLE and PEBBLE_COPY_ONCE env vars set.
159+
container = pod["spec"]["containers"][1]
160+
env = [e["value"] for e in container["env"] if e["name"] == PEBBLE_ENV]
161+
assert len(env) == 1 and env[0] == PEBBLE_DEFAULT_WRITABLE_DIR
162+
163+
env = [e["value"] for e in container["env"] if e["name"] == PEBBLE_ENV_COPY_ONCE]
164+
assert len(env) == 1 and env[0] == PEBBLE_DEFAULT_DIR
165+
166+
# The read-only container should have the volume mount.
167+
volume_mounts = [mount["name"] for mount in container["volumeMounts"]]
168+
assert PEBBLE_VOLUME_NAME in volume_mounts
169+
170+
# Redundant check, the Pod should have the volume.
171+
volume_names = [vol["name"] for vol in pod["spec"]["volumes"]]
172+
assert PEBBLE_VOLUME_NAME in volume_names
173+
174+
175+
def test_webhook_update_envs(webhook_instance: harness.Instance):
176+
pod = _apply_pod(webhook_instance, "pod-update-envs.yaml")
177+
container = pod["spec"]["containers"][0]
178+
overwritten_pebble_path = "/var/lib/foo/lish"
179+
180+
# The container should have updated PEBBLE and PEBBLE_COPY_ONCE env vars.
181+
env = [e["value"] for e in container["env"] if e["name"] == PEBBLE_ENV]
182+
assert len(env) == 1 and env[0] == os.path.join(
183+
overwritten_pebble_path, PEBBLE_WRITABLE_SUBPATH
184+
)
185+
186+
env = [e["value"] for e in container["env"] if e["name"] == PEBBLE_ENV_COPY_ONCE]
187+
assert len(env) == 1 and env[0] == overwritten_pebble_path
188+
189+
# The read-only container should have the volume mount.
190+
volume_mounts = [mount["name"] for mount in container["volumeMounts"]]
191+
assert PEBBLE_VOLUME_NAME in volume_mounts
192+
193+
# Redundant check, the Pod should have the volume.
194+
volume_names = [vol["name"] for vol in pod["spec"]["volumes"]]
195+
assert PEBBLE_VOLUME_NAME in volume_names
196+
197+
198+
def test_webhook_rock(webhook_instance: harness.Instance):
199+
pod = _apply_pod(webhook_instance, "pod-rock.yaml")
200+
201+
# The container has "readOnlyRootFilesystem=true". Without the webhook, Pebble wouldn't
202+
# be able to start because it cannot create the files it needs.
203+
# Get the Pod logs. Pebble should now be able to start properly and start the service.
204+
process = webhook_instance.exec(
205+
["k8s", "kubectl", "logs", pod["metadata"]["name"]],
206+
capture_output=True,
207+
text=True,
208+
)
209+
210+
assert 'Service "mutating-pebble-webhook" starting:' in process.stdout

tests/templates/pod-has-mount.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: test-has-mount
5+
spec:
6+
containers:
7+
- name: test
8+
image: busybox:1.28
9+
command: ["sleep", "3600"]
10+
volumeMounts:
11+
- name: foo
12+
mountPath: /var/lib/pebble/default
13+
securityContext:
14+
readOnlyRootFilesystem: true
15+
volumes:
16+
- name: foo
17+
emptyDir: {}

tests/templates/pod-mixed.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: test-mixed
5+
spec:
6+
containers:
7+
- name: test-normal
8+
image: busybox:1.28
9+
command: ["sleep", "3600"]
10+
- name: test-readonly
11+
image: busybox:1.28
12+
command: ["sleep", "3600"]
13+
securityContext:
14+
readOnlyRootFilesystem: true

tests/templates/pod-noop.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: test-noop
5+
spec:
6+
containers:
7+
- name: test
8+
image: busybox:1.28
9+
command: ["sleep", "3600"]

tests/templates/pod-rock.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: test-rock
5+
spec:
6+
containers:
7+
- name: test-rock
8+
image: ghcr.io/canonical/mutating-pebble-webhook:0.0.1-ck0
9+
securityContext:
10+
readOnlyRootFilesystem: true

tests/templates/pod-update-envs.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: test-update-envs
5+
spec:
6+
containers:
7+
- name: test
8+
image: busybox:1.28
9+
command: ["sleep", "3600"]
10+
env:
11+
- name: PEBBLE
12+
value: "/var/lib/foo/lish"
13+
securityContext:
14+
readOnlyRootFilesystem: true

0 commit comments

Comments
 (0)