blob: 0fea178a78ec397875e028ebe7a9c0d2fb9ed73a [file]
#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Validate the artifact shipped to Helm, beyond source template tests."""
from pathlib import Path
import re
import shutil
import subprocess
import tarfile
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
CHART = ROOT / "helm" / "hugegraph"
PACKAGE_BUDGET = 512 * 1024
class HelmPackageTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
if shutil.which("helm") is None:
raise RuntimeError("Helm is required for the packaged chart regression")
temporary = tempfile.TemporaryDirectory(prefix="hugegraph-chart-")
cls.addClassCleanup(temporary.cleanup)
cls.directory = Path(temporary.name)
cls.version = re.search(r"(?m)^version: (.+)$",
(CHART / "Chart.yaml").read_text()).group(1)
subprocess.run(["helm", "package", str(CHART), "--destination", str(cls.directory)],
check=True, capture_output=True, text=True)
packages = list(cls.directory.glob("*.tgz"))
if len(packages) != 1:
raise AssertionError("Expected exactly one packaged chart")
cls.package = packages[0]
with tarfile.open(cls.package, "r:gz") as archive:
cls.files = {}
for member in archive.getmembers():
if member.isfile():
name = member.name.removeprefix("hugegraph-helm/")
cls.files[name] = archive.extractfile(member).read()
def test_distribution_identity(self):
self.assertEqual(f"hugegraph-helm-{self.version}.tgz", self.package.name)
self.assertRegex(self.files["Chart.yaml"].decode(),
r"(?m)^name: hugegraph-helm$")
rendered = subprocess.run(
["helm", "template", "hugegraph", str(self.package)],
check=True, capture_output=True, text=True).stdout
self.assertIn("name: hugegraph-server\n", rendered)
self.assertIn('app.kubernetes.io/name: "hugegraph"', rendered)
self.assertIn(f"helm.sh/chart: hugegraph-helm-{self.version}", rendered)
def test_archive_budget(self):
self.assertLess(self.package.stat().st_size, PACKAGE_BUDGET,
"Chart exceeds its package budget; check non-runtime assets")
def test_no_contributor_or_binary_documentation_files(self):
excluded = ("images/", "tests/", "scripts/", "testdata/")
leaked = [name for name in self.files
if name.startswith(excluded) or name.endswith(".tgz")]
self.assertEqual([], leaked,
"Development assets must not enter the Helm release payload")
def test_required_configuration_is_preserved(self):
required = ("Chart.yaml", "README.md", "values.yaml", "values.schema.json",
"values-single.yaml", "values-cluster.yaml", "values-strict.yaml",
"docs/operations.md", "docs/distribution.md", "examples/README.md")
for name in required:
with self.subTest(file=name):
self.assertIn(name, self.files)
# Helm normalizes Chart.yaml and values.yaml while packaging.
if name not in ("Chart.yaml", "values.yaml"):
self.assertEqual((CHART / name).read_bytes(), self.files[name])
def test_every_runtime_template_is_preserved(self):
for source in (CHART / "templates").rglob("*"):
if source.is_file():
name = source.relative_to(CHART).as_posix()
with self.subTest(template=name):
self.assertIn(name, self.files)
self.assertEqual(source.read_bytes(), self.files[name])
def test_internal_inventory_covers_workloads_and_helpers(self):
example = (CHART / "examples" / "README.md").read_text()
inventory = example.split("## Internal images", 1)[1].split("`global.imageRegistry`", 1)[0]
images = set(re.findall(r"`(hugegraph/[^`]+:[^`]+)`", inventory))
self.assertEqual(4, len(images), "Document all four default component images")
registry = "registry.internal.example.com"
rendered = subprocess.run(
["helm", "template", "inventory-check", str(self.package),
"--values", str(CHART / "examples" / "values-internal-registry.yaml")],
check=True, capture_output=True, text=True).stdout
selected = set(re.findall(r'^\s+image:\s*["\']?([^"\'\s]+)', rendered, re.MULTILINE))
self.assertEqual({f"{registry}/{image}" for image in images}, selected,
"Mirror inventory must cover workloads, init containers and test hooks")
for name in ("README.md", "examples/values-internal-registry.yaml"):
text = (CHART / name).read_text()
for image in images:
self.assertIn(image.split(":", 1)[0], text,
f"{name} omits a required image repository")
def test_packaged_chart_renders_each_preset(self):
for preset in (None, "values-single.yaml", "values-cluster.yaml", "values-strict.yaml"):
with self.subTest(preset=preset or "default"):
command = ["helm", "template", "package-check", str(self.package)]
if preset:
# Use the values shipped inside the archive, not the source tree.
values = self.directory / preset
values.write_bytes(self.files[preset])
command.extend(["--values", str(values)])
rendered = subprocess.run(command, check=True, capture_output=True)
self.assertTrue(b"kind: StatefulSet" in rendered.stdout,
"Packaged chart did not render a StatefulSet")
self.assertTrue(b"kind: Deployment" in rendered.stdout,
"Packaged chart did not render a Deployment")
if __name__ == "__main__":
unittest.main()