def test_env_secret(): app = { "id": "foobarify", "env": { "FOO": { "secret": "bar" } }, "secrets": { "bar": { "source": "/deadbeef/baz" } }, } settings = app_translator.Settings( app_translator.ContainerDefaults(image="lazybox", working_dir=None), app_secret_mapping=TrackingAppSecretMapping(app['id'], app['secrets']), ) translated = app_translator.translate_app(app, settings) env = translated.deployment['spec']['template']['spec']['containers'][0]['env'] assert env == [{ 'name': 'FOO', 'valueFrom': { 'secretKeyRef': { 'name': 'marathonsecret-foobarify', 'key': 'deadbeef.baz', } } }]
def new_settings(image: str = "busybox"): return app_translator.Settings( app_translator.ContainerDefaults( image=image, working_dir=".", ), app_secret_mapping=DummyAppSecretMapping(), )
def test_fetch_fails_without_working_dir(): settings = app_translator.Settings( app_translator.ContainerDefaults( image="busybox", working_dir=None, ), app_secret_mapping=DummyAppSecretMapping(), ) fields = {"id": "app", "fetch": [{"uri": "http://foobar.baz/0xdeadbeef"}]} with pytest.raises(app_translator.AdditionalFlagNeeded, match=r'.*?--container-working-dir.*?'): app_translator.translate_app(fields, settings)
def test_generated_fetch_layout(): """ Tests generation of volume and init container for `fetch`. NOTE: This neither tests the generation of the fetch script that runs in the init container, nor ensures that this script itself actually works! """ settings = app_translator.Settings( app_translator.ContainerDefaults( image="busybox", working_dir="/fetched_artifacts", ), app_secret_mapping=DummyAppSecretMapping(), ) fields = {"id": "app", "fetch": [{"uri": "http://foobar.baz/0xdeadbeef"}]} translated = app_translator.translate_app(fields, settings) template_spec = translated.deployment['spec']['template']['spec'] # The volume for fetching artifacts should be an empty dir. fetch_volume_name = template_spec['volumes'][0]['name'] assert template_spec['volumes'] == [{ 'emptyDir': {}, 'name': fetch_volume_name }] # Ensure that the fetch volume will be mounted into the main container as a working dir. assert template_spec['containers'][0]['volumeMounts'] ==\ [{'name': fetch_volume_name, 'mountPath': settings.container_defaults.working_dir}] # The fetcher itself should be implemented as a SINGLE init container. assert len(template_spec['initContainers']) == 1 fetch_container = template_spec['initContainers'][0] assert fetch_container['volumeMounts'] == \ [{'name': fetch_volume_name, 'mountPath': fetch_container['workingDir']}] # TODO (asekretenko): Write an integration test for the fetch command! assert isinstance(fetch_container['command'], list)
import pytest from dcos_migrate.plugins.marathon import app_translator from .common import DummyAppSecretMapping EMPTY_SETTINGS = app_translator.Settings( app_translator.ContainerDefaults( image="busybox", working_dir=None, ), app_secret_mapping=DummyAppSecretMapping(), ) def test_command_health_check_with_all_fields_set(): app = { "id": "/healthy", "healthChecks": [{ "protocol": "COMMAND", "command": { "value": "exit 0" }, "gracePeriodSeconds": 123, "intervalSeconds": 45, "timeoutSeconds": 99, "maxConsecutiveFailures": 333 }], }
def test_host_path_volume_with_fetch(): """ Tests that emitting a volume for fetch artifacts does not interfere with a hostPath volume translation. """ settings = app_translator.Settings( app_translator.ContainerDefaults( image=None, working_dir="/sandbox", ), app_secret_mapping=DummyAppSecretMapping(), ) app = { "id": "app", "container": { "docker": { "image": "python" }, "volumes": [ { "containerPath": "/ro", "hostPath": "/volumes/ro", "mode": "RO" }, ], }, "fetch": [{ "uri": "http://foobar.baz/0xdeadbeef" }], } translated = app_translator.translate_app(app, settings) template_spec = translated.deployment['spec']['template']['spec'] volumes = sorted(template_spec['volumes'], key=lambda v: v['name']) assert volumes == [ { "name": "fetch-artifacts", 'emptyDir': {} }, { "name": "volume-0", 'hostPath': { "path": "/volumes/ro" } }, ] mounts = sorted(template_spec['containers'][0]['volumeMounts'], key=lambda v: v['name']) assert mounts == [ { "name": "fetch-artifacts", "mountPath": "/sandbox" }, { "name": "volume-0", "mountPath": "/ro", "readOnly": True }, ]
def test_host_path_volumes(): settings = app_translator.Settings( app_translator.ContainerDefaults( image=None, working_dir=None, ), app_secret_mapping=DummyAppSecretMapping(), ) app = { "id": "app", "container": { "docker": { "image": "python" }, "volumes": [ { "containerPath": "/rw", "hostPath": "/volumes/rw", "mode": "RW" }, { "containerPath": "/ro", "hostPath": "/volumes/ro", "mode": "RO" }, { "containerPath": "/foo", "hostPath": "relative_to_sandbox", "mode": "RO" }, { "containerPath": "foo", # we cannot fully translate this persistent volume because there is no mapping into the container "persistent": { "size": 1024, "type": "root" }, "mode": "RO" }, ], }, } translated = app_translator.translate_app(app, settings) template_spec = translated.deployment['spec']['template']['spec'] volumes = sorted(template_spec['volumes'], key=lambda v: v['name']) assert volumes == [{ 'name': 'volume-0', 'hostPath': { 'path': '/volumes/rw' } }, { 'name': 'volume-1', 'hostPath': { 'path': '/volumes/ro' } }] mounts = sorted(template_spec['containers'][0]['volumeMounts'], key=lambda v: v['name']) assert mounts == [ { "name": "volume-0", "mountPath": "/rw", "readOnly": False }, { "name": "volume-1", "mountPath": "/ro", "readOnly": True }, ] # For now, we do not translate volumes with a "hostPath" relative to # Mesos sandbox (which typically are a part of a persistent volume setup). # Persistent volumes themselves aren't translated either. volume_warnings = [ w for w in translated.warnings if "Cannot translate a volume" in w ] assert len(volume_warnings) == 2 assert any("relative_to_sandbox" in w for w in volume_warnings) assert any("persistent" in w for w in volume_warnings)
def test_multiple_secret_volumes(): """ Tests a secret volume. One of the main things covered is non-interference between generating secret and host path volumes. """ app = { "id": "foobarify", "container": { "docker": { "image": "python" }, "volumes": [ { "containerPath": "/etc/foo", "secret": "foo-secret" }, { "containerPath": "/run/bar", "secret": "bar-secret" }, { "containerPath": "/var/baz", "secret": "baz-secret" }, ], }, "secrets": { "foo-secret": { "source": "foo" }, "bar-secret": { "source": "bar" }, "baz-secret": { "source": "baz" }, }, } settings = app_translator.Settings( app_translator.ContainerDefaults(image=None, working_dir=None), app_secret_mapping=TrackingAppSecretMapping(app['id'], app['secrets']), ) translated = app_translator.translate_app(app, settings) template_spec = translated.deployment['spec']['template']['spec'] volumes = sorted(template_spec['volumes'], key=lambda v: v['name']) assert volumes == [{ "name": "secrets-marathonsecret-foobarify", "secret": { "secretName": "marathonsecret-foobarify", "items": [ { "key": "foo", "path": "foo", "mode": 0o777 }, { "key": "bar", "path": "bar", "mode": 0o777 }, { "key": "baz", "path": "baz", "mode": 0o777 }, ], } }] name = volumes[0]['name'] mounts = sorted(template_spec['containers'][0]['volumeMounts'], key=lambda v: v['name']) assert mounts == [ { "name": name, "mountPath": "/etc/foo", "subPath": "foo", "readOnly": True }, { "name": name, "mountPath": "/run/bar", "subPath": "bar", "readOnly": True }, { "name": name, "mountPath": "/var/baz", "subPath": "baz", "readOnly": True }, ]