blob: e76f04dfd88b3d96937620a478db5e4859eac971 [file] [log] [blame]
# 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.
from __future__ import annotations
import json
import os
import subprocess
import tempfile
from importlib.util import find_spec
from pathlib import Path
import pytest
# isort:off (needed to workaround isort bug)
from docker_tests.command_utils import run_command
from docker_tests.constants import SOURCE_ROOT
from docker_tests.docker_tests_utils import (
display_dependency_conflict_message,
docker_image,
run_bash_in_docker,
run_python_in_docker,
)
# isort:on (needed to workaround isort bug)
from setup import PREINSTALLED_PROVIDERS
INSTALLED_PROVIDER_PATH = SOURCE_ROOT / "scripts" / "ci" / "installed_providers.txt"
class TestCommands:
def test_without_command(self):
"""Checking the image without a command. It should return non-zero exit code."""
with pytest.raises(subprocess.CalledProcessError) as ctx:
run_command(["docker", "run", "--rm", "-e", "COLUMNS=180", docker_image])
assert 2 == ctx.value.returncode
def test_airflow_command(self):
"""Checking 'airflow' command It should return non-zero exit code."""
with pytest.raises(subprocess.CalledProcessError) as ctx:
run_command(["docker", "run", "--rm", "-e", "COLUMNS=180", docker_image, "airflow"])
assert 2 == ctx.value.returncode
def test_airflow_version(self):
"""Checking 'airflow version' command It should return zero exit code."""
output = run_command(
["docker", "run", "--rm", "-e", "COLUMNS=180", docker_image, "airflow", "version"],
return_output=True,
)
assert "2." in output
def test_python_version(self):
"""Checking 'python --version' command It should return zero exit code."""
output = run_command(
["docker", "run", "--rm", "-e", "COLUMNS=180", docker_image, "python", "--version"],
return_output=True,
)
assert "Python 3." in output
def test_bash_version(self):
"""Checking 'bash --version' command It should return zero exit code."""
output = run_command(
["docker", "run", "--rm", "-e", "COLUMNS=180", docker_image, "bash", "--version"],
return_output=True,
)
assert "GNU bash," in output
class TestPythonPackages:
def test_required_providers_are_installed(self):
if os.environ.get("TEST_SLIM_IMAGE"):
lines = PREINSTALLED_PROVIDERS
else:
lines = (d.strip() for d in INSTALLED_PROVIDER_PATH.read_text().splitlines())
lines = (d for d in lines)
packages_to_install = {f"apache-airflow-providers-{d.replace('.', '-')}" for d in lines}
assert len(packages_to_install) != 0
output = run_bash_in_docker(
"airflow providers list --output json", stderr=subprocess.DEVNULL, return_output=True
)
providers = json.loads(output)
packages_installed = {d["package_name"] for d in providers}
assert len(packages_installed) != 0
assert packages_to_install == packages_installed, (
f"List of expected installed packages and image content mismatch. "
f"Check {INSTALLED_PROVIDER_PATH} file."
)
def test_pip_dependencies_conflict(self):
try:
run_bash_in_docker("pip check")
except subprocess.CalledProcessError as ex:
display_dependency_conflict_message()
raise ex
PACKAGE_IMPORTS = {
"amazon": ["boto3", "botocore", "watchtower"],
"async": ["gevent", "eventlet", "greenlet"],
"azure": [
"azure.batch",
"azure.cosmos",
"azure.datalake.store",
"azure.identity",
"azure.keyvault.secrets",
"azure.kusto.data",
"azure.mgmt.containerinstance",
"azure.mgmt.datalake.store",
"azure.mgmt.resource",
"azure.storage",
],
"celery": ["celery", "flower", "vine"],
"cncf.kubernetes": ["kubernetes", "cryptography"],
"dask": ["cloudpickle", "distributed"],
"docker": ["docker"],
"elasticsearch": ["elasticsearch", "es.elastic", "elasticsearch_dsl"],
"google": [
"OpenSSL",
# "google.ads", Remove google ads as it is vendored in google provider now
"googleapiclient",
"google.auth",
"google_auth_httplib2",
"google.cloud.automl",
"google.cloud.bigquery_datatransfer",
"google.cloud.bigtable",
"google.cloud.container",
"google.cloud.datacatalog",
"google.cloud.dataproc",
"google.cloud.dlp",
"google.cloud.kms",
"google.cloud.language",
"google.cloud.logging",
"google.cloud.memcache",
"google.cloud.monitoring",
"google.cloud.oslogin",
"google.cloud.pubsub",
"google.cloud.redis",
"google.cloud.secretmanager",
"google.cloud.spanner",
"google.cloud.speech",
"google.cloud.storage",
"google.cloud.tasks",
"google.cloud.texttospeech",
"google.cloud.translate",
"google.cloud.videointelligence",
"google.cloud.vision",
],
"grpc": ["grpc", "google.auth", "google_auth_httplib2"],
"hashicorp": ["hvac"],
"ldap": ["ldap"],
"postgres": ["psycopg2"],
"pyodbc": ["pyodbc"],
"redis": ["redis"],
"sendgrid": ["sendgrid"],
"sftp/ssh": ["paramiko", "sshtunnel"],
"slack": ["slack_sdk"],
"statsd": ["statsd"],
"virtualenv": ["virtualenv"],
}
if bool(find_spec("mysql")):
PACKAGE_IMPORTS["mysql"] = ["mysql"]
@pytest.mark.skipif(os.environ.get("TEST_SLIM_IMAGE") == "true", reason="Skipped with slim image")
@pytest.mark.parametrize("package_name,import_names", PACKAGE_IMPORTS.items())
def test_check_dependencies_imports(self, package_name, import_names):
run_python_in_docker(f"import {','.join(import_names)}")
class TestExecuteAsRoot:
def test_execute_airflow_as_root(self):
run_command(
[
"docker",
"run",
"--rm",
"--user",
"0",
"-e",
"PYTHONDONTWRITEBYTECODE=true",
docker_image,
"airflow",
"info",
]
)
def test_run_custom_python_packages_as_root(self):
with tempfile.TemporaryDirectory() as tmp_dir:
(Path(tmp_dir) / "__init__.py").write_text("")
(Path(tmp_dir) / "awesome.py").write_text('print("Awesome")')
run_command(
[
"docker",
"run",
"--rm",
"-e",
f"PYTHONPATH={tmp_dir}",
"-e",
"PYTHONDONTWRITEBYTECODE=true",
"-v",
f"{tmp_dir}:{tmp_dir}",
"--user",
"0",
docker_image,
"python",
"-c",
"import awesome",
]
)