def _get_networks() -> List[_Network]: """Gets information about existing docker networks. Returns: A slice containing one entry for each docker network. Default networks (e.g., bridge and host) are included. Raises: plumbum.commands.processes.ProcessExecutionError: One of the docker commands returns a non-zero exit code. """ nets = [] net_json = cmd.docker('network', 'ls', '-q', '--format={{json .}}') for net_json in net_json.splitlines(): net = json.loads(net_json) net_inspect_json = cmd.docker('network', 'inspect', '--format={{json .}}', net['ID']) net_inspect = json.loads(net_inspect_json) containers = [] for container_id, prop in net_inspect['Containers'].items(): ipv4 = prop.get('IPv4Address', '') ipv6 = prop.get('IPv6Address', '') containers.append('%s: %s %s' % (prop['Name'], ipv4, ipv6)) nets.append( _Network(net_inspect['Name'], net_inspect['Driver'], containers)) return nets
def proxy(**env_vars): """A context manager that starts the proxy with the specified env. While inside the block, `$DOCKER_HOST` will be modified to talk to the proxy instead of the raw docker socket. """ container_id = None env_list = [f"--env={key}={value}" for key, value in env_vars.items()] info(f"Starting {DOCKER_IMAGE_NAME} container with: {env_list}") try: container_id = docker( "container", "run", "--detach", "--privileged", "--publish=2375", "--volume=/var/run/docker.sock:/var/run/docker.sock", *env_list, DOCKER_IMAGE_NAME, ).strip() container_data = json.loads( docker("container", "inspect", container_id.strip())) socket_port = container_data[0]["NetworkSettings"]["Ports"][ "2375/tcp"][0]["HostPort"] with local.env(DOCKER_HOST=f"tcp://localhost:{socket_port}"): yield container_id finally: if container_id: info(f"Removing {container_id}...") docker( "container", "rm", "-f", container_id, )
def _proxy(**env_vars): container_id = None env_list = [f"--env={key}={value}" for key, value in env_vars.items()] _logger.info(f"Starting {image} container with: {env_list}") try: container_id = docker( "container", "run", "--detach", "--privileged", "--publish=2375", "--volume=/var/run/docker.sock:/var/run/docker.sock", *env_list, image, ).strip() container_data = json.loads( docker("container", "inspect", container_id.strip())) socket_port = container_data[0]["NetworkSettings"]["Ports"][ "2375/tcp"][0]["HostPort"] with local.env(DOCKER_HOST=f"tcp://localhost:{socket_port}"): yield container_id finally: if container_id: _logger.info(f"Removing {container_id}...") docker( "container", "rm", "-f", container_id, )
def traefik_host(docker: LocalCommand, request): """Fixture to indicate where to find a running traefik instance.""" traefik_run = docker[ "container", "run", "--detach", "--privileged", "--network=inverseproxy_shared", "--volume=/var/run/docker.sock:/var/run/docker.sock:ro", f"traefik:{request.param}", ] try: if request.param == "latest" or version.parse( request.param) >= version.parse("2"): traefik_container = traefik_run( "--entrypoints.web-main.address=:80", "--log.level=debug", "--providers.docker.exposedByDefault=false", "--providers.docker.network=inverseproxy_shared", "--providers.docker=true", ).strip() else: traefik_container = traefik_run( "--logLevel=debug", "--defaultEntryPoints=http", "--docker.exposedByDefault=false", "--docker.watch", "--docker", "--entryPoints=Name:http Address::80 Compress:on", ).strip() traefik_details = json.loads( docker("container", "inspect", traefik_container)) assert (len(traefik_details) == 1 ), "Impossible... did you trigger a race condition?" yield traefik_details[0]["NetworkSettings"]["Networks"][ "inverseproxy_shared"]["IPAddress"] finally: docker("container", "rm", "--force", traefik_container)
def setup_prepare(self): """Unpacks loads local docker images and generates the topology. """ # Delete old artifacts, if any. cmd.rm("-rf", self.test_state.artifacts) cmd.mkdir(self.test_state.artifacts) print("artifacts dir: %s" % self.test_state.artifacts) for tar in self.test_state.containers_tars: print(cmd.docker("image", "load", "-i", tar)) for loader in self.test_state.container_loaders: parts = loader.split("#") if len(parts) != 2: logger.error("Invalid container loader argument: %s, ignored" % loader) continue tag, script = parts[0], parts[1] o = subprocess.check_output([script]).decode("utf-8") idx = o.index("as ") if idx < 0: logger.error("extracting tag from loader script %s" % loader) continue bazel_tag = o[idx + len("as "):].strip() logger.info("docker tag %s %s" % (bazel_tag, tag)) subprocess.run(["docker", "tag", bazel_tag, tag], check=True) # Define where coredumps will be stored. print( cmd.docker("run", "--rm", "--privileged", "alpine", "sysctl", "-w", "kernel.core_pattern=/share/coredump")) self._setup_generate()
def test_docker_bin(container_factory): for tag in ["docker-s3", "docker"]: with container_factory(tag) as test_container: docker( "exec", test_container, "docker", "--version", )
def cexec(image): """Return an exec shorthand for a running ci-base container.""" cid = None try: cid = docker("container", "run", "--detach", image, "sleep", "3600") yield docker["container", "exec", cid.strip()] finally: if cid: docker("container", "rm", "--force", cid.strip())
def traefik_host(docker: LocalCommand, request): """Fixture to indicate where to find a running traefik instance.""" traefik_run = docker[ "container", "run", "--detach", "--privileged", "--network=inverseproxy_shared", "--volume=/var/run/docker.sock:/var/run/docker.sock:ro", f"traefik:{request.param}", ] try: if request.param == "latest" or version.parse(request.param) >= version.parse( "2" ): traefik_container = traefik_run( "--accessLog=true", "--entrypoints.web-alt.address=:8080", "--entrypoints.web-insecure.address=:80", "--entrypoints.web-main.address=:443", "--log.level=debug", "--providers.docker.exposedByDefault=false", "--providers.docker.network=inverseproxy_shared", "--providers.docker=true", ).strip() else: traefik_container = traefik_run( "--defaultEntryPoints=web-insecure,web-main", "--docker.exposedByDefault=false", "--docker.watch", "--docker", "--entryPoints=Name:web-alt Address::8080 Compress:on", "--entryPoints=Name:web-insecure Address::80 Redirect.EntryPoint:web-main", "--entryPoints=Name:web-main Address::443 Compress:on TLS TLS.minVersion:VersionTLS12", "--logLevel=debug", ).strip() traefik_details = json.loads(docker("container", "inspect", traefik_container)) assert ( len(traefik_details) == 1 ), "Impossible... did you trigger a race condition?" interesting_details = { "ip": traefik_details[0]["NetworkSettings"]["Networks"][ "inverseproxy_shared" ]["IPAddress"], "traefik_version": traefik_details[0]["Config"]["Labels"][ "org.opencontainers.image.version" ], "traefik_image": traefik_details[0]["Image"], } interesting_details["hostname"] = f"{interesting_details['ip']}.sslip.io" yield interesting_details # Make sure there were no errors or warnings in logs traefik_logs = docker("container", "logs", traefik_container) assert " level=error " not in traefik_logs assert " level=warn " not in traefik_logs finally: docker("container", "rm", "--force", traefik_container)
def tearDown(self): try: print("Postgres container logs:") docker["container", "logs", self.postgres_container] & FG docker["container", "stop", self.postgres_container] & FG docker["container", "rm", self.postgres_container] & FG except AttributeError: pass # No postgres daemon docker("network", "rm", "lan", "wan") return super().tearDown()
def _connect_wan_network(self, alias="example.com"): """Bind a new network, to imitate WAN connections.""" docker( "network", "connect", "--alias", alias, "wan", self.postgres_container, )
def _setup_container_loaders(self): for tag, script in self.container_loaders: o = local[script]() idx = o.index("as ") if idx < 0: logger.error("extracting tag from loader script %s" % tag) continue bazel_tag = o[idx + len("as "):].strip() logger.info("docker tag %s %s" % (bazel_tag, tag)) cmd.docker("tag", bazel_tag, tag)
def test_containers_start(container_factory): for tag in [ "docker-s3", "postgres-s3", "docker", "postgres", "s3", "base" ]: with container_factory(tag) as test_container: docker( "exec", test_container, "dup", "--version", )
def setup_prepare(self): """Unpacks the topology and loads local docker images. """ # Delete old artifacts, if any. cmd.rm("-rf", self.test_state.artifacts) cmd.mkdir(self.test_state.artifacts) print('artifacts dir: %s' % self.test_state.artifacts) self._unpack_topo() print(cmd.docker('image', 'load', '-i', self.test_state.containers_tar)) # Define where coredumps will be stored. print( cmd.docker("run", "--rm", "--privileged", "alpine", "sysctl", "-w", "kernel.core_pattern=/share/coredump"))
def setUp(self): with local.cwd(local.cwd / ".."): print("Building image") local["./hooks/build"] & FG docker("network", "create", "lan") docker("network", "create", "wan") self.version = os.environ["DOCKER_TAG"] self.image = "tecnativa/postgres-autoconf:{}".format(self.version), self.cert_files = { "client.ca.cert.pem", "server.cert.pem", "server.key.pem", } return super().setUp()
def main(self): tmpdir = tempfile.mkdtemp(prefix='service_filelog.') print( docker('image', 'load', '-i', './integration/service_filelog/cs.tar')) name = 'service_filelog_test' try: args = [ 'run', '--name', name, '-v', '{}:/share/logs'.format(tmpdir), 'bazel/integration/service_filelog:cs' ] print('docker', *args) print(docker(*args, retcode=1)) logfile = Path('{}/cs.log'.format(tmpdir)) if not logfile.is_file(): print('{} is not a file'.format(logfile)) sys.exit(1) if logfile.stat().st_size == 0: print('{} is empty'.format(logfile)) sys.exit(1) finally: docker('logs', name) docker('rm', '-f', name) docker('rmi', 'bazel/integration/service_filelog:cs') shutil.rmtree(tmpdir, ignore_errors=True)
def setup_prepare(self): """Unpacks loads local docker images and generates the topology. """ # Delete old artifacts, if any. cmd.rm("-rf", self.test_state.artifacts) cmd.mkdir(self.test_state.artifacts) print("artifacts dir: %s" % self.test_state.artifacts) for tar in self.test_state.containers_tars: print(cmd.docker("image", "load", "-i", tar)) # Define where coredumps will be stored. print( cmd.docker("run", "--rm", "--privileged", "alpine", "sysctl", "-w", "kernel.core_pattern=/share/coredump")) self._setup_generate()
def test_server_certs_mount(self): """El test server habilita la autenticación cert a través de montajes de archivos.""" with local.tempdir() as tdir: with local.cwd(tdir): self._generate_certs() cert_vols = [ "-v{0}/{1}:/etc/postgres/{1}".format(local.cwd, cert) for cert in [ "client.ca.cert.pem", "server.cert.pem", "server.key.pem", ] ] self.postgres_container = docker( "container", "run", "-d", "--network", "lan", "-e", "POSTGRES_DB=test_db", "-e", "POSTGRES_PASSWORD=test_password", "-e", "POSTGRES_USER=test_user", CONF_EXTRA, *cert_vols, self.image, ).strip() self._check_local_connection() self._check_password_auth() self._connect_wan_network() self._check_cert_auth()
def execute(self, isd_as: ISD_AS, cmd: str, *args: str) -> str: expanded = [] for arg in args: if str(arg).startswith('gen/'): arg = '/share/' + arg expanded.append(arg) return docker('exec', 'tester_%s' % isd_as.file_fmt(), cmd, *expanded)
def _check_cert_auth(self): """Test connection with cert auth work fine.""" # Test connection with cert auth works fine self.assertEqual( "1\n", docker( "container", "run", "--network", "wan", "-e", "PGDATABASE=test_db", "-e", "PGSSLCERT=/certs/client.cert.pem", "-e", "PGSSLKEY=/certs/client.key.pem", "-e", "PGSSLMODE=verify-full", "-e", "PGSSLROOTCERT=/certs/server.ca.cert.pem", "-e", "PGUSER=test_user", CONF_EXTRA, "-v", "{}:/certs".format(local.cwd), self.image, "psql", "--host", "example.localdomain", "--command", "SELECT 1", "--no-align", "--tuples-only", ), )
def _check_local_connection(self): """Check that local connection works fine.""" # The 1st test could fail while postgres boots for attempt in range(10): try: time.sleep(5) # Test local connections via unix socket work self.assertEqual( "1\n", docker( "container", "exec", self.postgres_container, "psql", "--command", "SELECT 1", "--dbname", "test_db", "--no-align", "--tuples-only", "--username", "test_user", ), ) except AssertionError: if attempt < 9: print("Failure number {}. Retrying...".format(attempt)) else: raise else: continue
def test_no_certs_wan(self): """Unencrypted WAN access works (although this is dangerous).""" self.postgres_container = docker( "container", "run", "-d", "--network", "lan", "-e", "POSTGRES_DB=test_db", "-e", "POSTGRES_PASSWORD=test_password", "-e", "POSTGRES_USER=test_user", "-e", "WAN_AUTH_METHOD=md5", "-e", "WAN_CONNECTION=host", CONF_EXTRA, self.image, ).strip() self._check_local_connection() self._check_password_auth() self._connect_wan_network() with self.assertRaises(ProcessExecutionError): self._check_password_auth("example.localdomain")
def _check_password_auth(self, host=None): """Test connection with password auth work fine.""" if not host: # Connect via LAN by default host = self.postgres_container[:12] self.assertEqual( "1\n", docker( "container", "run", "--network", "lan", "-e", "PGDATABASE=test_db", "-e", "PGPASSWORD=test_password", "-e", "PGSSLMODE=disable", "-e", "PGUSER=test_user", self.image, "psql", "--host", host, "--command", "SELECT 1", "--no-align", "--tuples-only", ), )
def _setup(self): print( cmd.docker("image", "load", "-i", "./acceptance/hidden_paths/testcontainers.tar")) # TODO(scrye): Mangle configuration files of Daemons and Control Services to enable # hidden paths. print(self._docker_compose("up", "-d")) time.sleep(5) self._testers = { "2": "tester_1-ff00_0_2", "3": "tester_1-ff00_0_3", "4": "tester_1-ff00_0_4", "5": "tester_1-ff00_0_5", } self._ases = { "2": "1-ff00:0:2", "3": "1-ff00:0:3", "4": "1-ff00:0:4", "5": "1-ff00:0:5", } self._daemons_api = { "2": "172.20.0.52:30255", "3": "172.20.0.60:30255", "4": "172.20.0.68:30255", "5": "172.20.0.76:30255", }
def _check_cert_auth(self): """La conexión con cert auth funciona bien.""" # La prueba conexión con cert auth funciona bien self.assertEqual( "1\n", docker( "container", "run", "--network", "wan", "-e", "PGDATABASE=test_db", "-e", "PGSSLCERT=/certs/client.cert.pem", "-e", "PGSSLKEY=/certs/client.key.pem", "-e", "PGSSLMODE=verify-full", "-e", "PGSSLROOTCERT=/certs/server.ca.cert.pem", "-e", "PGUSER=test_user", CONF_EXTRA, "-v", "{}:/certs".format(local.cwd), self.image, "psql", "--host", "ejemplo.localdomain", "--command", "SELECT 1", "--no-align", "--tuples-only", ), )
def test_server_certs_var(self): """Prueba que el servidor habilita autenticación de cert a través de env vars.""" with local.tempdir() as tdir: with local.cwd(tdir): self._generate_certs() certs_var = {name: cat(name) for name in self.cert_files} self.postgres_container = docker( "container", "run", "-d", "--network", "lan", "-e", "CERTS=" + json.dumps(certs_var), "-e", "POSTGRES_DB=test_db", "-e", "POSTGRES_PASSWORD=test_password", "-e", "POSTGRES_USER=test_user", CONF_EXTRA, self.image, ).strip() self._check_local_connection() self._check_password_auth() self._connect_wan_network() self._check_cert_auth()
def _check_password_auth(self, host=None): """La conexión con contraseña de autenticación funciona bien.""" if not host: # Conectarse a través de LAN por defecto host = self.postgres_container[:12] self.assertEqual( "1\n", docker( "container", "run", "--network", "lan", "-e", "PGDATABASE=test_db", "-e", "PGPASSWORD=test_password", "-e", "PGSSLMODE=disable", "-e", "PGUSER=test_user", self.image, "psql", "--host", host, "--command", "SELECT 1", "--no-align", "--tuples-only", ), )
def _check_local_connection(self): """Verifica que la conexión local funcione bien.""" # La primera prueba podría fallar mientras bootea postgres for attempt in range(10): try: time.sleep(5) # Probar conexiones locales a través del trabajo de socket Unix self.assertEqual( "1\n", docker( "container", "exec", self.postgres_container, "psql", "--command", "SELECT 1", "--dbname", "test_db", "--no-align", "--tuples-only", "--username", "test_user", ), ) except AssertionError: if attempt < 9: print("Failure number {}. Retrying...".format(attempt)) else: raise else: continue
def test_certs_falsy_lan(self): """La configuración con valores falsos para certs funciona bien.""" self.postgres_container = docker( "container", "run", "-d", "--network", "lan", "-e", "POSTGRES_DB=test_db", "-e", "POSTGRES_PASSWORD=test_password", "-e", "POSTGRES_USER=test_user", CONF_EXTRA, "-e", "CERTS={}".format( json.dumps({ "client.ca.cert.pem": False, "server.cert.pem": False, "server.key.pem": False, })), self.image, ).strip() self._check_local_connection() self._check_password_auth() self._connect_wan_network() with self.assertRaises(ProcessExecutionError): self._check_password_auth("ejemplo.localdomain")
def run_ssh_server(ssh_public_key, target_docker_image): cleaned_docker_name = target_docker_image.replace(':', '_').replace('/', '_') target_container_name = "sshd_on_%s" % cleaned_docker_name # start ssh service on a new container based on target_docker_image Dodo.run([ 'docker', 'run', '-d', '--rm', '--publish=0.0.0.0:22:22', '--name=%s' % target_container_name, target_docker_image, '/usr/sbin/sshd', '-D', ]) # copy public key to the docker container Dodo.run([ 'docker', 'cp', ssh_public_key, '%s:/root/.ssh/authorized_keys' % target_container_name ]) # get the ip address target_ip = docker( 'inspect', '-f', '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}', target_container_name)[:-1] return target_ip, target_container_name
def test_no_certs_wan(self): """El acceso a WAN sin cifrar funciona (aunque esto es peligroso).""" self.postgres_container = docker( "container", "run", "-d", "--network", "lan", "-e", "POSTGRES_DB=test_db", "-e", "POSTGRES_PASSWORD=test_password", "-e", "POSTGRES_USER=test_user", "-e", "WAN_AUTH_METHOD=md5", "-e", "WAN_CONNECTION=host", CONF_EXTRA, self.image, ).strip() self._check_local_connection() self._check_password_auth() self._connect_wan_network() with self.assertRaises(ProcessExecutionError): self._check_password_auth("ejemplo.localdomain")