Skip to content

Commit

Permalink
Enforce k8s version constraints (#1990)
Browse files Browse the repository at this point in the history
* Put k8s version constraints into helm.

* Remove GitPython

* Use metadata.yaml in pytest and circleci. Add tests.

* add validate_objects arg to render_chart()

* Add tests

* Do not allow releasing to internal from non-release branches

---------

Co-authored-by: pgvishnuram <81585115+pgvishnuram@users.noreply.github.com>
  • Loading branch information
danielhoherd and pgvishnuram committed Sep 14, 2023
1 parent 26de31a commit 808d447
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 53 deletions.
8 changes: 8 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,17 @@ workflows:
- build-artifact
- approve-internal-release:
type: approval
filters:
branches:
only:
- '/^release-0\.\d+$/'
- release-to-internal:
context:
- gcp-astronomer-prod
filters:
branches:
only:
- '/^release-0\.\d+$/'
requires:
- approve-internal-release
- platform-1-23-17
Expand Down
8 changes: 8 additions & 0 deletions .circleci/config.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,17 @@ workflows:
{% endif %}{% endfor %}
- approve-internal-release:
type: approval
filters:
branches:
only:
- '/^release-0\.\d+$/'
- release-to-internal:
context:
- gcp-astronomer-prod
filters:
branches:
only:
- '/^release-0\.\d+$/'
requires:
- approve-internal-release
{%- for version in [kube_versions[0], kube_versions[-1]] %}
Expand Down
10 changes: 5 additions & 5 deletions .circleci/generate_circleci_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import os
import subprocess
from pathlib import Path
import yaml

from jinja2 import Template

# When adding a new version, look up the most
# recent patch version on Dockerhub
# This should match what is in tests/__init__.py
# https://hub.docker.com/r/kindest/node/tags
kube_versions = ["1.23.17", "1.24.15", "1.25.11", "1.26.6", "1.27.3"]
metadata = yaml.safe_load((Path(__file__).parents[1] / "metadata.yaml").read_text())
kube_versions = metadata["test_k8s_versions"]

# https://circleci.com/docs/2.0/building-docker-images/#docker-version
ci_remote_docker_version = "20.10.24"

# https://circleci.com/developer/machine/image/ubuntu-2204
machine_image_version = "ubuntu-2204:2023.07.2"
ci_runner_version = "2023-09"
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repos:
language: python
files: "config.yml$|config.yml.j2|generate_circleci_config.py$|\/values.yaml$"
entry: .circleci/generate_circleci_config.py
additional_dependencies: ["jinja2"]
additional_dependencies: ["jinja2", "PyYAML"]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
hooks:
Expand Down
8 changes: 8 additions & 0 deletions metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# When adding a new version, look up the most recent patch version on Dockerhub
# https://hub.docker.com/r/kindest/node/tags
test_k8s_versions:
- 1.23.17
- 1.24.15
- 1.25.11
- 1.26.6
- 1.27.3
1 change: 0 additions & 1 deletion requirements/chart-tests.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
docker
exceptiongroup # indirect, needed to satisfy hash requirement
filelock
GitPython
jmespath
jsonschema
kubernetes
Expand Down
31 changes: 9 additions & 22 deletions requirements/chart-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,9 @@ filelock==3.12.3 \
--hash=sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d \
--hash=sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb
# via -r requirements/chart-tests.in
gitdb==4.0.10 \
--hash=sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a \
--hash=sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7
# via gitpython
gitpython==3.1.34 \
--hash=sha256:5d3802b98a3bae1c2b8ae0e1ff2e4aa16bcdf02c145da34d092324f599f01395 \
--hash=sha256:85f7d365d1f6bf677ae51039c1ef67ca59091c7ebd5a3509aa399d4eda02d6dd
# via -r requirements/chart-tests.in
google-auth==2.22.0 \
--hash=sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce \
--hash=sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873
google-auth==2.23.0 \
--hash=sha256:2cec41407bd1e207f5b802638e32bb837df968bb5c05f413d0fa526fac4cf7a7 \
--hash=sha256:753a26312e6f1eaeec20bc6f2644a10926697da93446e1f8e24d6d32d45a922a
# via kubernetes
gprof2dot==2022.7.29 \
--hash=sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5 \
Expand Down Expand Up @@ -196,9 +188,9 @@ pygments==2.16.1 \
--hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
--hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
# via rich
pytest==7.4.1 \
--hash=sha256:2f2301e797521b23e4d2585a0a3d7b5e50fdddaaf7e7d6773ea26ddb17c213ab \
--hash=sha256:460c9a59b14e27c602eb5ece2e47bec99dc5fc5f6513cf924a7d03a578991b1f
pytest==7.4.2 \
--hash=sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002 \
--hash=sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069
# via
# -r requirements/chart-tests.in
# pytest-forked
Expand Down Expand Up @@ -420,14 +412,9 @@ six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# google-auth
# kubernetes
# pytest-profiling
# python-dateutil
smmap==5.0.0 \
--hash=sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94 \
--hash=sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936
# via gitdb
tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
Expand All @@ -444,9 +431,9 @@ urllib3==1.26.16 \
# google-auth
# kubernetes
# requests
websocket-client==1.6.2 \
--hash=sha256:53e95c826bf800c4c465f50093a8c4ff091c7327023b10bfaff40cf1ef170eaa \
--hash=sha256:ce54f419dfae71f4bdba69ebe65bf7f0a93fe71bc009ad3a010aacc3eebad537
websocket-client==1.6.3 \
--hash=sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f \
--hash=sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03
# via
# docker
# kubernetes
25 changes: 12 additions & 13 deletions requirements/functional-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ gitdb==4.0.10 \
--hash=sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a \
--hash=sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7
# via gitpython
gitpython==3.1.34 \
--hash=sha256:5d3802b98a3bae1c2b8ae0e1ff2e4aa16bcdf02c145da34d092324f599f01395 \
--hash=sha256:85f7d365d1f6bf677ae51039c1ef67ca59091c7ebd5a3509aa399d4eda02d6dd
gitpython==3.1.35 \
--hash=sha256:9cbefbd1789a5fe9bcf621bb34d3f441f3a90c8461d377f84eda73e721d9b06b \
--hash=sha256:c19b4292d7a1d3c0f653858db273ff8a6614100d1eb1528b014ec97286193c09
# via -r requirements/functional-tests.in
google-auth==2.22.0 \
--hash=sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce \
--hash=sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873
google-auth==2.23.0 \
--hash=sha256:2cec41407bd1e207f5b802638e32bb837df968bb5c05f413d0fa526fac4cf7a7 \
--hash=sha256:753a26312e6f1eaeec20bc6f2644a10926697da93446e1f8e24d6d32d45a922a
# via kubernetes
idna==3.4 \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
Expand Down Expand Up @@ -158,9 +158,9 @@ pygments==2.16.1 \
--hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
--hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
# via rich
pytest==7.4.1 \
--hash=sha256:2f2301e797521b23e4d2585a0a3d7b5e50fdddaaf7e7d6773ea26ddb17c213ab \
--hash=sha256:460c9a59b14e27c602eb5ece2e47bec99dc5fc5f6513cf924a7d03a578991b1f
pytest==7.4.2 \
--hash=sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002 \
--hash=sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069
# via
# -r requirements/functional-tests.in
# pytest-pretty
Expand Down Expand Up @@ -259,7 +259,6 @@ six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# google-auth
# kubernetes
# python-dateutil
smmap==5.0.0 \
Expand All @@ -274,9 +273,9 @@ urllib3==1.26.16 \
# google-auth
# kubernetes
# requests
websocket-client==1.6.2 \
--hash=sha256:53e95c826bf800c4c465f50093a8c4ff091c7327023b10bfaff40cf1ef170eaa \
--hash=sha256:ce54f419dfae71f4bdba69ebe65bf7f0a93fe71bc009ad3a010aacc3eebad537
websocket-client==1.6.3 \
--hash=sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f \
--hash=sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03
# via
# docker
# kubernetes
14 changes: 14 additions & 0 deletions templates/version_compatibility_check.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{- $metadata := .Files.Get "metadata.yaml" | fromYaml }}
{{- $versions := $metadata.test_k8s_versions }}
{{- $oldestMinor := regexReplaceAll "^(\\d+\\.\\d+).*" (index $versions 0) "${1}"}}
{{- $newestMinor := regexReplaceAll "^(\\d+\\.\\d+).*" (index $versions (sub (len $versions) 1)) "${1}"}}
{{- $err := (printf "\nThis version of Astronomer \"Software\" was tested on the following kubernetes versions:\n%s" ($versions | toYaml)) }}
{{ if and (semverCompare (printf "<%s" $oldestMinor) .Capabilities.KubeVersion.Version) (not .Values.forceIncompatibleKubernetes) -}}
{{- $err := (printf "%s\n\nkubernetes version %s is unsupported because it is too old! You must upgrade your kubernetes version before continuing." $err .Capabilities.KubeVersion.Version) }}
{{- $err := (printf "%s\n\nFor more details, refer to our documentation at https://docs.astronomer.io/software/release-lifecycle-policy" $err) }}
{{- fail (printf "ABORT!\n%s" $err) }}
{{ else if and (semverCompare (printf ">%s" $newestMinor) .Capabilities.KubeVersion.Version) (not .Values.forceIncompatibleKubernetes) -}}
{{- $err := (printf "%s\n\nkubernetes version %s is unsupported because it is too new! You must wait for a new version of Astronomer \"Software\" to be released that supports your version of kubernetes." $err .Capabilities.KubeVersion.Version) }}
{{- $err := (printf "%s\n\nFor more details, refer to our documentation at https://docs.astronomer.io/software/release-lifecycle-policy" $err) }}
{{- fail (printf "ABORT!\n%s" $err) }}
{{ end }}
19 changes: 12 additions & 7 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from pathlib import Path

import git
import yaml

# The top-level path of this repository
git_repo = git.Repo(__file__, search_parent_directories=True)
git_root_dir = Path(git_repo.git.rev_parse("--show-toplevel"))

# This should match the major.minor version list in .circleci/generate_circleci_config.py
# Patch version should always be 0
supported_k8s_versions = ["1.23.0", "1.24.0", "1.25.0", "1.26.0", "1.27.0"]
git_root_dir = [x for x in Path(__file__).resolve().parents if (x / ".git").is_dir()][
-1
]

metadata = yaml.safe_load((Path(git_root_dir) / "metadata.yaml").read_text())
# replace all patch versions with 0 so we end up with ['1.26.0', '1.27.0']
supported_k8s_versions = [
".".join(x.split(".")[:-1] + ["0"]) for x in metadata["test_k8s_versions"]
]
k8s_version_too_old = f'1.{str(int(supported_k8s_versions[0].split(".")[1]) - 1)}.0'
k8s_version_too_new = f'1.{str(int(supported_k8s_versions[-1].split(".")[1]) + 1)}.0'


def get_containers_by_name(doc, include_init_containers=False):
Expand Down
8 changes: 5 additions & 3 deletions tests/chart_tests/helm_template_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def render_chart(
kube_version: str = "1.24.0",
baseDomain: str = "example.com",
namespace: Optional[str] = None,
validate_objects: bool = True,
):
"""Render a helm chart into dictionaries.
Expand Down Expand Up @@ -137,9 +138,10 @@ def render_chart(
)
raise
k8s_objects = yaml.full_load_all(templates)
k8s_objects = [k8s_object for k8s_object in k8s_objects if k8s_object] # type: ignore
for k8s_object in k8s_objects:
validate_k8s_object(k8s_object, kube_version=kube_version)
k8s_objects: list = [k8s_object for k8s_object in k8s_objects if k8s_object]
if validate_objects:
for k8s_object in k8s_objects:
validate_k8s_object(k8s_object, kube_version=kube_version)
return k8s_objects


Expand Down
51 changes: 50 additions & 1 deletion tests/chart_tests/test_default_chart.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
import tests.chart_tests as chart_tests
from tests import get_containers_by_name
from tests import get_containers_by_name, k8s_version_too_old, k8s_version_too_new
from tests.chart_tests.helm_template_generator import render_chart
import re

Expand All @@ -10,6 +10,55 @@
pod_managers = ["Deployment", "StatefulSet", "DaemonSet"]


class TestK8sVersionConstraints:
@pytest.mark.parametrize(
"version,err_substring",
[
(k8s_version_too_old, "too old!"),
(k8s_version_too_new, "too new!"),
],
)
def test_k8s_version_out_of_bounds(self, version, err_substring):
"""Test that an error is returned when the k8s version is too old or too new."""
with pytest.raises(Exception) as err:
render_chart(
kube_version=version,
)
assert err_substring in str(err.value.stderr.decode())

@pytest.mark.parametrize(
"version",
[
(k8s_version_too_old),
(k8s_version_too_new),
],
)
def test_k8s_version_out_of_bounds_override(self, version):
"""Test that no error is returned for versions that are too old or new when forceIncompatibleKubernetes is used."""
render_chart(
values={"forceIncompatibleKubernetes": True},
kube_version=version,
validate_objects=False,
)


class TestAllCronJobs:
chart_values = chart_tests.get_all_features()

def test_ensure_cronjob_names_are_max_52_chars(self):
"""Cronjob names must be DNS_MAX_LEN - TIMESTAMP_LEN, which is 52 chars."""
default_docs = render_chart(values=self.chart_values)
cronjobs = [
doc for doc in default_docs if doc["kind"].lower() == "CronJob".lower()
]

for doc in cronjobs:
name_len = len(doc["metadata"]["name"])
assert (
name_len <= 52
), f'{doc["metadata"]["name"]} is too long at {name_len} characters'


class TestAllPodSpecContainers:
"""Test pod spec containers for some defaults."""

Expand Down

0 comments on commit 808d447

Please sign in to comment.