|
| 1 | +#!/usr/bin/env python |
| 2 | +import argparse |
| 3 | +import base64 |
| 4 | +import json |
| 5 | +import os |
| 6 | +import subprocess |
| 7 | +import uuid |
| 8 | + |
| 9 | +import CloudFlare |
| 10 | +import yaml |
| 11 | +from retrying import retry |
| 12 | + |
| 13 | +from constants import MAX_RETRIES, BACKOFF_SECS |
| 14 | +from util import LOGGER |
| 15 | + |
| 16 | + |
| 17 | +def get_config_from_env(): |
| 18 | + config_content = base64.b64decode(get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8') |
| 19 | + return yaml.safe_load(config_content) |
| 20 | + |
| 21 | + |
| 22 | +def get_config_from_file(): |
| 23 | + config_path = get_env("COMPONENT_TESTS_CONFIG") |
| 24 | + with open(config_path, 'r') as infile: |
| 25 | + return yaml.safe_load(infile) |
| 26 | + |
| 27 | + |
| 28 | +def persist_config(config): |
| 29 | + config_path = get_env("COMPONENT_TESTS_CONFIG") |
| 30 | + with open(config_path, 'w') as outfile: |
| 31 | + yaml.safe_dump(config, outfile) |
| 32 | + |
| 33 | + |
| 34 | +def persist_origin_cert(config): |
| 35 | + origincert = get_env("COMPONENT_TESTS_ORIGINCERT") |
| 36 | + path = config["origincert"] |
| 37 | + with open(path, 'w') as outfile: |
| 38 | + outfile.write(origincert) |
| 39 | + return path |
| 40 | + |
| 41 | + |
| 42 | +@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) |
| 43 | +def create_tunnel(config, origincert_path, random_uuid): |
| 44 | + # Delete any previous existing credentials file. If the agent keeps files around (that's the case in Windows) then |
| 45 | + # cloudflared tunnel create will refuse to create the tunnel because it does not want to overwrite credentials |
| 46 | + # files. |
| 47 | + credentials_path = config["credentials_file"] |
| 48 | + try: |
| 49 | + os.remove(credentials_path) |
| 50 | + except OSError: |
| 51 | + pass |
| 52 | + |
| 53 | + tunnel_name = "cfd_component_test-" + random_uuid |
| 54 | + create_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "create", |
| 55 | + "--credentials-file", credentials_path, tunnel_name] |
| 56 | + LOGGER.info(f"Creating tunnel with {create_cmd}") |
| 57 | + subprocess.run(create_cmd, check=True) |
| 58 | + |
| 59 | + list_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "list", "--name", |
| 60 | + tunnel_name, "--output", "json"] |
| 61 | + LOGGER.info(f"Listing tunnel with {list_cmd}") |
| 62 | + cloudflared = subprocess.run(list_cmd, check=True, capture_output=True) |
| 63 | + return json.loads(cloudflared.stdout)[0]["id"] |
| 64 | + |
| 65 | + |
| 66 | +@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) |
| 67 | +def delete_tunnel(config): |
| 68 | + credentials_path = config["credentials_file"] |
| 69 | + delete_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", config["origincert"], "delete", |
| 70 | + "--credentials-file", credentials_path, "-f", config["tunnel"]] |
| 71 | + LOGGER.info(f"Deleting tunnel with {delete_cmd}") |
| 72 | + subprocess.run(delete_cmd, check=True) |
| 73 | + |
| 74 | + |
| 75 | +@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) |
| 76 | +def create_dns(config, hostname, type, content): |
| 77 | + cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN")) |
| 78 | + cf.zones.dns_records.post( |
| 79 | + config["zone_tag"], |
| 80 | + data={'name': hostname, 'type': type, 'content': content, 'proxied': True} |
| 81 | + ) |
| 82 | + |
| 83 | + |
| 84 | +def create_classic_dns(config, random_uuid): |
| 85 | + classic_hostname = "classic-" + random_uuid + "." + config["zone_domain"] |
| 86 | + create_dns(config, classic_hostname, "AAAA", "fd10:aec2:5dae::") |
| 87 | + return classic_hostname |
| 88 | + |
| 89 | + |
| 90 | +def create_named_dns(config, random_uuid): |
| 91 | + hostname = "named-" + random_uuid + "." + config["zone_domain"] |
| 92 | + create_dns(config, hostname, "CNAME", config["tunnel"] + ".cfargotunnel.com") |
| 93 | + return hostname |
| 94 | + |
| 95 | + |
| 96 | +@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) |
| 97 | +def delete_dns(config, hostname): |
| 98 | + cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN")) |
| 99 | + zone_tag = config["zone_tag"] |
| 100 | + dns_records = cf.zones.dns_records.get(zone_tag, params={'name': hostname}) |
| 101 | + if len(dns_records) > 0: |
| 102 | + cf.zones.dns_records.delete(zone_tag, dns_records[0]['id']) |
| 103 | + |
| 104 | + |
| 105 | +def write_file(content, path): |
| 106 | + with open(path, 'w') as outfile: |
| 107 | + outfile.write(content) |
| 108 | + |
| 109 | + |
| 110 | +def get_env(env_name): |
| 111 | + val = os.getenv(env_name) |
| 112 | + if val is None: |
| 113 | + raise Exception(f"{env_name} is not set") |
| 114 | + return val |
| 115 | + |
| 116 | + |
| 117 | +def create(): |
| 118 | + """ |
| 119 | + Creates the necessary resources for the components test to run. |
| 120 | + - Creates a named tunnel with a random name. |
| 121 | + - Creates a random CNAME DNS entry for that tunnel. |
| 122 | + - Creates a random AAAA DNS entry for a classic tunnel. |
| 123 | +
|
| 124 | + Those created resources are added to the config (obtained from an environment variable). |
| 125 | + The resulting configuration is persisted for the tests to use. |
| 126 | + """ |
| 127 | + config = get_config_from_env() |
| 128 | + origincert_path = persist_origin_cert(config) |
| 129 | + |
| 130 | + random_uuid = str(uuid.uuid4()) |
| 131 | + config["tunnel"] = create_tunnel(config, origincert_path, random_uuid) |
| 132 | + config["classic_hostname"] = create_classic_dns(config, random_uuid) |
| 133 | + config["ingress"] = [ |
| 134 | + { |
| 135 | + "hostname": create_named_dns(config, random_uuid), |
| 136 | + "service": "hello_world" |
| 137 | + }, |
| 138 | + { |
| 139 | + "service": "http_status:404" |
| 140 | + } |
| 141 | + ] |
| 142 | + |
| 143 | + persist_config(config) |
| 144 | + |
| 145 | + |
| 146 | +def cleanup(): |
| 147 | + """ |
| 148 | + Reads the persisted configuration that was created previously. |
| 149 | + Deletes the resources that were created there. |
| 150 | + """ |
| 151 | + config = get_config_from_file() |
| 152 | + delete_tunnel(config) |
| 153 | + delete_dns(config, config["classic_hostname"]) |
| 154 | + delete_dns(config, config["ingress"][0]["hostname"]) |
| 155 | + |
| 156 | + |
| 157 | +if __name__ == '__main__': |
| 158 | + parser = argparse.ArgumentParser(description='setup component tests') |
| 159 | + parser.add_argument('--type', choices=['create', 'cleanup'], default='create') |
| 160 | + args = parser.parse_args() |
| 161 | + |
| 162 | + if args.type == 'create': |
| 163 | + create() |
| 164 | + else: |
| 165 | + cleanup() |
0 commit comments