diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 1f2d550..077b96b 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -12,95 +12,62 @@ jobs: steps: - name: Check out code uses: actions/checkout@v2 - - name: Install dependencies run: | - sudo apt-get install python3-pip tox - + pip install tox - name: Lint code - run: tox -e lint + run: tox -vve lint unit: name: Unit Test runs-on: ubuntu-latest steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Install dependencies - run: | - sudo apt-get install python3-pip tox + - name: Check out code + uses: actions/checkout@v2 + - name: Install dependencies + run: | + pip install tox - - name: Run unit tests - run: tox -e unit + - name: Run unit tests + run: tox -vve unit - deploy: - name: Deploy Test + integration: + name: Integration Test (deploy and access) runs-on: ubuntu-latest steps: - - name: Check out repo - uses: actions/checkout@v2 - - - uses: balchua/microk8s-actions@v0.2.2 - with: - addons: '["dns", "storage", "rbac"]' - - - name: Install dependencies - run: | - set -eux - sudo pip3 install charmcraft==1.0.0 - sudo snap install juju --classic - sudo snap install juju-wait --classic - sudo snap install yq - - # Avoid race condition with storage taking a long time to initialize - - name: Wait for storage - run: | - sg microk8s -c 'microk8s kubectl rollout status deployment/hostpath-provisioner -n kube-system' - - - name: Bootstrap Juju - run: | - set -eux - sg microk8s -c 'juju bootstrap microk8s uk8s' - juju add-model minio - - - name: Deploy MinIO - run: | - set -eux - charmcraft build -v - juju deploy ./minio.charm \ - --config secret-key=minio-secret-key \ - --resource oci-image=$(yq eval '.resources.oci-image.upstream-source' metadata.yaml) - juju wait -wvt 300 - - - name: Test MinIO - run: | - kubectl run \ - --rm \ - -i \ - --restart=Never \ - --command sh \ - --image=minio/mc \ - -- \ - sh -c \ - "mc alias set ci http://minio.minio.svc.cluster.local:9000 minio minio-secret-key && mc mb ci/foo && mc rb ci/foo" - - - - name: Get all - run: kubectl get all -A - if: failure() - - - name: Get juju status - run: juju status - if: failure() - - - name: Get minio logs - run: kubectl logs --tail 100 -nminio -ljuju-app=minio - if: failure() - - - name: Get minio operator logs - run: kubectl logs --tail 100 -nminio -ljuju-operator=minio - if: failure() - + - name: Check out code + uses: actions/checkout@v2 + - name: Setup operator environment + uses: charmed-kubernetes/actions-operator@main + with: + provider: microk8s + + # TODO: Remove once the actions-operator does this automatically + - name: Configure kubectl + run: | + sg microk8s -c "microk8s config > ~/.kube/config" + + - name: Build and test + run: | + sg microk8s -c "tox -vve integration" + + # On failure, capture debugging resources + - name: Get all + run: kubectl get all -A + if: failure() + + - name: Get juju status + run: juju status + if: failure() + + # TODO: How can we capture this if using pytest operator? Create our own model and keep it + # after tests end? +# - name: Get minio logs +# run: kubectl logs --tail 100 -nminio -ljuju-app=minio +# if: failure() +# +# - name: Get minio operator logs +# run: kubectl logs --tail 100 -nminio -ljuju-operator=minio +# if: failure() diff --git a/requirements.txt b/requirements.txt index 4a382af..495ca49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ ops==1.1.0 -git+https://github.com/juju-solutions/resource-oci-image@1964d748022b762b9dce6e8bb7bdf12835102c72 +oci-image serialized-data-interface<0.4 diff --git a/test-requirements.txt b/test-requirements.txt index ae3022f..35aaeb5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,2 @@ pytest -flake8 -black==20.8b1 +pyyaml diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py new file mode 100644 index 0000000..66639e8 --- /dev/null +++ b/tests/integration/test_charm.py @@ -0,0 +1,84 @@ +import logging +from pathlib import Path + +import pytest +import yaml +from pytest_operator.plugin import OpsTest + +log = logging.getLogger(__name__) + +METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) + +MINIO_CONFIG = { + "access-key": "minio", + "secret-key": "minio-secret-key", +} + +APP_NAME = "minio" +CHARM_ROOT = "." + + +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest): + built_charm_path = await ops_test.build_charm(CHARM_ROOT) + log.info(f"Built charm {built_charm_path}") + + image_path = METADATA["resources"]["oci-image"]["upstream-source"] + resources = {"oci-image": image_path} + + await ops_test.model.deploy( + entity_url=built_charm_path, + resources=resources, + config=MINIO_CONFIG, + ) + await ops_test.model.wait_for_idle(timeout=60 * 10) + + +async def test_connect_client_to_server(ops_test: OpsTest): + """ + Tests a deployed MinIO app by trying to connect to it from a pod and do trivial actions with it + """ + # TODO: This presumes we're using microk8s. Fix that. + # could just call kubectl and count on the outside environment aliasing it for us? + # or could pass kubectl path in as a variable? + + application = ops_test.model.applications[APP_NAME] + config = await application.get_config() + port = config["port"]["value"] + alias = "ci" + bucket = "testbucket" + # TODO: how do I dynamically retrieve the minio k8s service name? + service_name = APP_NAME + model_name = ops_test.model_name + log.info(f"ops_test.model_name = {ops_test.model_name}") + + url = f"http://{service_name}.{model_name}.svc.cluster.local:{port}" + + minio_cmd = ( + f"mc alias set {alias} {url} {MINIO_CONFIG['access-key']} {MINIO_CONFIG['secret-key']}" + f"&& mc mb {alias}/{bucket}" + f"&& mc rb {alias}/{bucket}" + ) + + kubectl_cmd = ( + "microk8s", + "kubectl", + "run", + "--rm", + "-i", + "--restart=Never", + "--command", + f"--namespace={ops_test.model_name}", + "minio-deployment-test", + "--image=minio/mc", + "--", + "sh", + "-c", + minio_cmd, + ) + + ret_code, stdout, stderr = await ops_test.run(*kubectl_cmd) + + assert ( + ret_code == 0 + ), f"Test returned code {ret_code} with stdout:\n{stdout}\nstderr:\n{stderr}" diff --git a/tests/test_charm.py b/tests/unit/test_charm.py similarity index 100% rename from tests/test_charm.py rename to tests/unit/test_charm.py diff --git a/tox.ini b/tox.ini index 9208d1c..8801dbd 100644 --- a/tox.ini +++ b/tox.ini @@ -11,11 +11,19 @@ deps = -rtest-requirements.txt -rrequirements.txt +[testenv:integration] +deps = + {[testenv]deps} + pytest-operator +commands = pytest -v --tb native --show-capture=no --log-cli-level=INFO -s {posargs} {toxinidir}/tests/integration + [testenv:unit] -commands = - pytest -v tests +commands = pytest -v {toxinidir}/tests/unit [testenv:lint] +deps = + flake8 + black==20.8b1 commands = flake8 {toxinidir}/src {toxinidir}/tests black --check {toxinidir}/src {toxinidir}/tests