Skip to content

Adds support to run lightweight kubernetes testcontainer using k3s #313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 28, 2023
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
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
- rabbitmq
- redis
- selenium
- k3s
runs-on: ${{ matrix.runtime.machine }}
steps:
- uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
rabbitmq/README
redis/README
selenium/README
k3s/README

Getting Started
---------------
Expand Down
1 change: 1 addition & 0 deletions k3s/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.. autoclass:: testcontainers.k3s.K3SContainer
19 changes: 19 additions & 0 deletions k3s/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from setuptools import setup, find_namespace_packages

description = "K3S component of testcontainers-python."

setup(
name="testcontainers-k3s",
version="0.0.1rc1",
packages=find_namespace_packages(),
description=description,
long_description=description,
long_description_content_type="text/x-rst",
url="https://github.com/testcontainers/testcontainers-python",
install_requires=[
"testcontainers-core",
"kubernetes",
"pyyaml"
],
python_requires=">=3.7",
)
65 changes: 65 additions & 0 deletions k3s/testcontainers/k3s/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#
# Licensed 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.

from testcontainers.core.config import MAX_TRIES
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_for_logs


class K3SContainer(DockerContainer):
"""
K3S container.

Example:

.. doctest::

>>> import yaml
>>> from testcontainers.k3s import K3SContainer
>>> from kubernetes import client, config

>>> with K3SContainer() as k3s:
... config.load_kube_config_from_dict(yaml.safe_load(k3s.config_yaml()))
... pod = client.CoreV1Api().list_pod_for_all_namespaces(limit=1)
... assert len(pod.items) > 0, "Unable to get running nodes from k3s cluster"
"""

KUBE_SECURE_PORT = 6443
RANCHER_WEBHOOK_PORT = 8443

def __init__(self, image="rancher/k3s:latest", **kwargs) -> None:
super(K3SContainer, self).__init__(image, **kwargs)
self.with_exposed_ports(self.KUBE_SECURE_PORT, self.RANCHER_WEBHOOK_PORT)
self.with_env("K3S_URL", f'https://{self.get_container_host_ip()}:{self.KUBE_SECURE_PORT}')
self.with_command("server --disable traefik --tls-san=" + self.get_container_host_ip())
self.with_kwargs(privileged=True, tmpfs={"/run": "", "/var/run": ""})
self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw")

def _connect(self) -> None:
wait_for_logs(self, predicate="Node controller sync successful", timeout=MAX_TRIES)

def start(self) -> "K3SContainer":
super().start()
self._connect()
return self

def config_yaml(self) -> str:
"""This function returns the kubernetes config yaml which can be used
to initialise k8s client
"""
execution = self.get_wrapped_container().exec_run(['cat', '/etc/rancher/k3s/k3s.yaml'])
config_yaml = execution.output.decode('utf-8') \
.replace(f'https://127.0.0.1:{self.KUBE_SECURE_PORT}',
f'https://{self.get_container_host_ip()}:'
f'{self.get_exposed_port(self.KUBE_SECURE_PORT)}')
return config_yaml
12 changes: 12 additions & 0 deletions k3s/tests/test_k3s.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# The versions below were the current supported versions at time of writing (2022-08-11)
import yaml
from kubernetes import client, config

from testcontainers.k3s import K3SContainer


def test_docker_run_k3s():
with K3SContainer() as k3s:
config.load_kube_config_from_dict(yaml.safe_load(k3s.config_yaml()))
pod = client.CoreV1Api().list_pod_for_all_namespaces(limit=1)
assert len(pod.items) > 0, "Unable to get running nodes from k3s cluster"
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
-e file:rabbitmq
-e file:redis
-e file:selenium
-e file:k3s
cryptography<37
flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages.
pg8000
Expand Down
55 changes: 40 additions & 15 deletions requirements/macos-latest-3.10.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# testcontainers-clickhouse
# testcontainers-elasticsearch
# testcontainers-gcp
# testcontainers-k3s
# testcontainers-kafka
# testcontainers-keycloak
# testcontainers-localstack
Expand All @@ -40,6 +41,8 @@
# via -r requirements.in
-e file:google
# via -r requirements.in
-e file:k3s
# via -r requirements.in
-e file:kafka
# via -r requirements.in
-e file:keycloak
Expand Down Expand Up @@ -90,16 +93,17 @@ azure-storage-blob==12.19.0
# via testcontainers-azurite
babel==2.13.1
# via sphinx
boto3==1.29.1
boto3==1.33.1
# via testcontainers-localstack
botocore==1.32.1
botocore==1.33.1
# via
# boto3
# s3transfer
cachetools==5.3.2
# via google-auth
certifi==2023.7.22
certifi==2023.11.17
# via
# kubernetes
# minio
# opensearch-py
# requests
Expand Down Expand Up @@ -137,7 +141,7 @@ ecdsa==0.18.0
# via python-jose
entrypoints==0.3
# via flake8
exceptiongroup==1.1.3
exceptiongroup==1.2.0
# via
# pytest
# trio
Expand All @@ -149,7 +153,9 @@ google-api-core[grpc]==2.14.0
# google-api-core
# google-cloud-pubsub
google-auth==2.23.4
# via google-api-core
# via
# google-api-core
# kubernetes
google-cloud-pubsub==2.18.4
# via testcontainers-gcp
googleapis-common-protos[grpc]==1.61.0
Expand All @@ -161,20 +167,20 @@ greenlet==3.0.1
# via sqlalchemy
grpc-google-iam-v1==0.12.7
# via google-cloud-pubsub
grpcio==1.59.2
grpcio==1.59.3
# via
# google-api-core
# google-cloud-pubsub
# googleapis-common-protos
# grpc-google-iam-v1
# grpcio-status
grpcio-status==1.59.2
grpcio-status==1.59.3
# via
# google-api-core
# google-cloud-pubsub
h11==0.14.0
# via wsproto
idna==3.4
idna==3.6
# via
# requests
# trio
Expand All @@ -201,6 +207,8 @@ kafka-python==2.0.2
# via testcontainers-kafka
keyring==24.3.0
# via twine
kubernetes==28.1.0
# via testcontainers-k3s
markdown-it-py==3.0.0
# via rich
markupsafe==2.1.3
Expand All @@ -213,11 +221,15 @@ minio==7.2.0
# via testcontainers-minio
more-itertools==10.1.0
# via jaraco-classes
neo4j==5.14.1
neo4j==5.15.0
# via testcontainers-neo4j
nh3==0.2.14
# via readme-renderer
opensearch-py==2.4.1
oauthlib==3.2.2
# via
# kubernetes
# requests-oauthlib
opensearch-py==2.4.2
# via testcontainers-opensearch
outcome==1.3.0.post0
# via trio
Expand Down Expand Up @@ -248,7 +260,7 @@ protobuf==4.25.1
# proto-plus
psycopg2-binary==2.9.9
# via testcontainers-postgres
pyasn1==0.5.0
pyasn1==0.5.1
# via
# pyasn1-modules
# python-jose
Expand All @@ -263,7 +275,7 @@ pycryptodome==3.19.0
# via minio
pyflakes==2.1.1
# via flake8
pygments==2.16.1
pygments==2.17.2
# via
# readme-renderer
# rich
Expand All @@ -289,6 +301,7 @@ python-arango==7.8.1
python-dateutil==2.8.2
# via
# botocore
# kubernetes
# opensearch-py
# pg8000
python-jose==3.3.0
Expand All @@ -299,6 +312,10 @@ pytz==2023.3.post1
# via
# clickhouse-driver
# neo4j
pyyaml==6.0.1
# via
# kubernetes
# testcontainers-k3s
readme-renderer==42.0
# via twine
redis==5.0.1
Expand All @@ -308,12 +325,16 @@ requests==2.31.0
# azure-core
# docker
# google-api-core
# kubernetes
# opensearch-py
# python-arango
# python-keycloak
# requests-oauthlib
# requests-toolbelt
# sphinx
# twine
requests-oauthlib==1.3.1
# via kubernetes
requests-toolbelt==1.0.0
# via
# python-arango
Expand All @@ -327,7 +348,7 @@ rsa==4.9
# via
# google-auth
# python-jose
s3transfer==0.7.0
s3transfer==0.8.0
# via boto3
scramp==1.4.4
# via pg8000
Expand All @@ -338,6 +359,7 @@ six==1.16.0
# azure-core
# ecdsa
# isodate
# kubernetes
# opensearch-py
# python-dateutil
sniffio==1.3.0
Expand Down Expand Up @@ -395,6 +417,7 @@ urllib3[socks]==1.26.18
# via
# botocore
# docker
# kubernetes
# minio
# opensearch-py
# python-arango
Expand All @@ -403,8 +426,10 @@ urllib3[socks]==1.26.18
# testcontainers-core
# twine
websocket-client==1.6.4
# via docker
wheel==0.41.3
# via
# docker
# kubernetes
wheel==0.42.0
# via -r requirements.in
wrapt==1.16.0
# via testcontainers-core
Expand Down
Loading