#!/usr/bin/env python import argparse import base64 import json import os import subprocess import uuid import CloudFlare import yaml from retrying import retry from constants import MAX_RETRIES, BACKOFF_SECS from util import LOGGER def get_config_from_env(): config_content = base64.b64decode(get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8') return yaml.safe_load(config_content) def get_config_from_file(): config_path = get_env("COMPONENT_TESTS_CONFIG") with open(config_path, 'r') as infile: return yaml.safe_load(infile) def persist_config(config): config_path = get_env("COMPONENT_TESTS_CONFIG") with open(config_path, 'w') as outfile: yaml.safe_dump(config, outfile) def persist_origin_cert(config): origincert = get_env("COMPONENT_TESTS_ORIGINCERT") path = config["origincert"] with open(path, 'w') as outfile: outfile.write(origincert) return path @retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) def create_tunnel(config, origincert_path, random_uuid): # Delete any previous existing credentials file. If the agent keeps files around (that's the case in Windows) then # cloudflared tunnel create will refuse to create the tunnel because it does not want to overwrite credentials # files. credentials_path = config["credentials_file"] try: os.remove(credentials_path) except OSError: pass tunnel_name = "cfd_component_test-" + random_uuid create_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "create", "--credentials-file", credentials_path, tunnel_name] LOGGER.info(f"Creating tunnel with {create_cmd}") subprocess.run(create_cmd, check=True) list_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "list", "--name", tunnel_name, "--output", "json"] LOGGER.info(f"Listing tunnel with {list_cmd}") cloudflared = subprocess.run(list_cmd, check=True, capture_output=True) return json.loads(cloudflared.stdout)[0]["id"] @retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) def delete_tunnel(config): credentials_path = config["credentials_file"] delete_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", config["origincert"], "delete", "--credentials-file", credentials_path, "-f", config["tunnel"]] LOGGER.info(f"Deleting tunnel with {delete_cmd}") subprocess.run(delete_cmd, check=True) @retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) def create_dns(config, hostname, type, content): cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN")) cf.zones.dns_records.post( config["zone_tag"], data={'name': hostname, 'type': type, 'content': content, 'proxied': True} ) def create_classic_dns(config, random_uuid): classic_hostname = "classic-" + random_uuid + "." + config["zone_domain"] create_dns(config, classic_hostname, "AAAA", "fd10:aec2:5dae::") return classic_hostname def create_named_dns(config, random_uuid): hostname = "named-" + random_uuid + "." + config["zone_domain"] create_dns(config, hostname, "CNAME", config["tunnel"] + ".cfargotunnel.com") return hostname @retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) def delete_dns(config, hostname): cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN")) zone_tag = config["zone_tag"] dns_records = cf.zones.dns_records.get(zone_tag, params={'name': hostname}) if len(dns_records) > 0: cf.zones.dns_records.delete(zone_tag, dns_records[0]['id']) def write_file(content, path): with open(path, 'w') as outfile: outfile.write(content) def get_env(env_name): val = os.getenv(env_name) if val is None: raise Exception(f"{env_name} is not set") return val def create(): """ Creates the necessary resources for the components test to run. - Creates a named tunnel with a random name. - Creates a random CNAME DNS entry for that tunnel. - Creates a random AAAA DNS entry for a classic tunnel. Those created resources are added to the config (obtained from an environment variable). The resulting configuration is persisted for the tests to use. """ config = get_config_from_env() origincert_path = persist_origin_cert(config) random_uuid = str(uuid.uuid4()) config["tunnel"] = create_tunnel(config, origincert_path, random_uuid) config["classic_hostname"] = create_classic_dns(config, random_uuid) config["ingress"] = [ { "hostname": create_named_dns(config, random_uuid), "service": "hello_world" }, { "service": "http_status:404" } ] persist_config(config) def cleanup(): """ Reads the persisted configuration that was created previously. Deletes the resources that were created there. """ config = get_config_from_file() delete_tunnel(config) delete_dns(config, config["classic_hostname"]) delete_dns(config, config["ingress"][0]["hostname"]) if __name__ == '__main__': parser = argparse.ArgumentParser(description='setup component tests') parser.add_argument('--type', choices=['create', 'cleanup'], default='create') args = parser.parse_args() if args.type == 'create': create() else: cleanup()