TUN-4062: Read component tests config from yaml file
This commit is contained in:
parent
206523344f
commit
a7344435a5
|
@ -215,6 +215,8 @@ stretch: &stretch
|
||||||
pre-cache:
|
pre-cache:
|
||||||
- sudo pip3 install --upgrade -r component-tests/requirements.txt
|
- sudo pip3 install --upgrade -r component-tests/requirements.txt
|
||||||
post-cache:
|
post-cache:
|
||||||
|
# Constructs config file from env vars
|
||||||
|
- python3 component-tests/config.py
|
||||||
- pytest component-tests
|
- pytest component-tests
|
||||||
update-homebrew:
|
update-homebrew:
|
||||||
builddeps:
|
builddeps:
|
||||||
|
|
|
@ -5,7 +5,17 @@
|
||||||
- `conda activate component-tests`
|
- `conda activate component-tests`
|
||||||
- `pip3 install -r requirements.txt`
|
- `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
2. Create a config yaml file, for example:
|
||||||
|
```
|
||||||
|
cloudflared_binary: "cloudflared"
|
||||||
|
tunnel: "3d539f97-cd3a-4d8e-c33b-65e9099c7a8d"
|
||||||
|
credentials_file: "/Users/tunnel/.cloudflared/3d539f97-cd3a-4d8e-c33b-65e9099c7a8d.json"
|
||||||
|
classic_hostname: "classic-tunnel-component-tests.example.com"
|
||||||
|
origincert: "/Users/tunnel/.cloudflared/cert.pem"
|
||||||
|
```
|
||||||
|
|
||||||
# How to run
|
# How to run
|
||||||
|
Specify path to config file via env var `COMPONENT_TESTS_CONFIG`. This is required.
|
||||||
## All tests
|
## All tests
|
||||||
Run `pytest` inside this(component-tests) folder
|
Run `pytest` inside this(component-tests) folder
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import base64
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from constants import METRICS_PORT
|
||||||
|
|
||||||
|
# frozen=True raises exception when assigning to fields. This emulates immutability
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TunnelBaseConfig:
|
||||||
|
no_autoupdate: bool = True
|
||||||
|
metrics: str = f'localhost:{METRICS_PORT}'
|
||||||
|
|
||||||
|
def merge_config(self, additional):
|
||||||
|
config = copy.copy(additional)
|
||||||
|
config['no-autoupdate'] = self.no_autoupdate
|
||||||
|
config['metrics'] = self.metrics
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NamedTunnelBaseConfig(TunnelBaseConfig):
|
||||||
|
# The attributes of the parent class are ordered before attributes in this class,
|
||||||
|
# so we have to use default values here and check if they are set in __post_init__
|
||||||
|
tunnel: str = None
|
||||||
|
credentials_file: str = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.tunnel is None:
|
||||||
|
raise TypeError("Field tunnel is not set")
|
||||||
|
if self.credentials_file is None:
|
||||||
|
raise TypeError("Field credentials_file is not set")
|
||||||
|
|
||||||
|
def merge_config(self, additional):
|
||||||
|
config = super(NamedTunnelBaseConfig, self).merge_config(additional)
|
||||||
|
config['tunnel'] = self.tunnel
|
||||||
|
config['credentials-file'] = self.credentials_file
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ClassicTunnelBaseConfig(TunnelBaseConfig):
|
||||||
|
hostname: str = None
|
||||||
|
origincert: str = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.hostname is None:
|
||||||
|
raise TypeError("Field tunnel is not set")
|
||||||
|
if self.origincert is None:
|
||||||
|
raise TypeError("Field credentials_file is not set")
|
||||||
|
|
||||||
|
def merge_config(self, additional):
|
||||||
|
config = super(ClassicTunnelBaseConfig, self).merge_config(additional)
|
||||||
|
config['hostnamel'] = self.hostname
|
||||||
|
config['origincert'] = self.origincert
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ComponentTestConfig:
|
||||||
|
cloudflared_binary: str
|
||||||
|
named_tunnel_config: dict
|
||||||
|
classic_tunnel_config: dict
|
||||||
|
|
||||||
|
|
||||||
|
def build_config_from_env():
|
||||||
|
config_path = get_env("COMPONENT_TESTS_CONFIG")
|
||||||
|
config_content = base64.b64decode(get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
|
||||||
|
config_yaml = yaml.safe_load(config_content)
|
||||||
|
|
||||||
|
credentials_file = get_env("COMPONENT_TESTS_CREDENTIALS_FILE")
|
||||||
|
write_file(credentials_file, config_yaml["credentials_file"])
|
||||||
|
|
||||||
|
origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
|
||||||
|
write_file(origincert,config_yaml["origincert"])
|
||||||
|
|
||||||
|
write_file(config_content, config_path)
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(content, path):
|
||||||
|
with open(path, 'w') as outfile:
|
||||||
|
outfile.write(content)
|
||||||
|
outfile.close
|
||||||
|
|
||||||
|
|
||||||
|
def get_env(env_name):
|
||||||
|
val = os.getenv(env_name)
|
||||||
|
if val is None:
|
||||||
|
raise Exception(f"{env_name} is not set")
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
build_config_from_env()
|
|
@ -0,0 +1,24 @@
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from config import ComponentTestConfig, NamedTunnelBaseConfig, ClassicTunnelBaseConfig
|
||||||
|
from util import LOGGER
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def component_tests_config():
|
||||||
|
config_file = os.getenv("COMPONENT_TESTS_CONFIG")
|
||||||
|
if config_file is None:
|
||||||
|
raise Exception("Need to provide path to config file in COMPONENT_TESTS_CONFIG")
|
||||||
|
with open(config_file, 'r') as stream:
|
||||||
|
config = yaml.safe_load(stream)
|
||||||
|
LOGGER.info(f"component tests base config {config}")
|
||||||
|
base_named_tunnel_config = NamedTunnelBaseConfig(tunnel=config['tunnel'], credentials_file=config['credentials_file'])
|
||||||
|
base_classic_tunnel_config = ClassicTunnelBaseConfig(hostname=config['classic_hostname'], origincert=config['origincert'])
|
||||||
|
|
||||||
|
def _component_tests_config(extra_named_tunnel_config={}, extra_classic_tunnel_config={}):
|
||||||
|
named_tunnel_config = base_named_tunnel_config.merge_config(extra_named_tunnel_config)
|
||||||
|
classic_tunnel_config = base_classic_tunnel_config.merge_config(extra_classic_tunnel_config)
|
||||||
|
return ComponentTestConfig(config['cloudflared_binary'], named_tunnel_config, classic_tunnel_config)
|
||||||
|
|
||||||
|
return _component_tests_config
|
|
@ -0,0 +1 @@
|
||||||
|
METRICS_PORT = 51000
|
|
@ -3,9 +3,8 @@ from util import start_cloudflared
|
||||||
|
|
||||||
class TestConfig:
|
class TestConfig:
|
||||||
# tmp_path is a fixture provides a temporary directory unique to the test invocation
|
# tmp_path is a fixture provides a temporary directory unique to the test invocation
|
||||||
def test_validate_ingress_rules(self, tmp_path):
|
def test_validate_ingress_rules(self, tmp_path, component_tests_config):
|
||||||
config = {
|
extra_config = {
|
||||||
'metrics': 'localhost:50000',
|
|
||||||
'ingress': [
|
'ingress': [
|
||||||
{
|
{
|
||||||
"hostname": "example.com",
|
"hostname": "example.com",
|
||||||
|
@ -31,23 +30,25 @@ class TestConfig:
|
||||||
{"service": "http_status:404"}
|
{"service": "http_status:404"}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
component_tests_config = component_tests_config(extra_config)
|
||||||
validate_args = ["ingress", "validate"]
|
validate_args = ["ingress", "validate"]
|
||||||
pre_args = ["tunnel"]
|
pre_args = ["tunnel"]
|
||||||
validate = start_cloudflared(tmp_path, config, validate_args, pre_args)
|
validate = start_cloudflared(tmp_path, component_tests_config, validate_args, pre_args)
|
||||||
assert validate.returncode == 0, "failed to validate ingress" + validate.stderr.decode("utf-8")
|
assert validate.returncode == 0, "failed to validate ingress" + validate.stderr.decode("utf-8")
|
||||||
|
|
||||||
self.match_rule(tmp_path, config, "http://example.com/index.html", 1)
|
self.match_rule(tmp_path, component_tests_config, "http://example.com/index.html", 1)
|
||||||
self.match_rule(tmp_path, config, "https://example.com/index.html", 1)
|
self.match_rule(tmp_path, component_tests_config, "https://example.com/index.html", 1)
|
||||||
self.match_rule(tmp_path, config, "https://api.example.com/login", 2)
|
self.match_rule(tmp_path, component_tests_config, "https://api.example.com/login", 2)
|
||||||
self.match_rule(tmp_path, config, "https://wss.example.com", 3)
|
self.match_rule(tmp_path, component_tests_config, "https://wss.example.com", 3)
|
||||||
self.match_rule(tmp_path, config, "https://ssh.example.com", 4)
|
self.match_rule(tmp_path, component_tests_config, "https://ssh.example.com", 4)
|
||||||
self.match_rule(tmp_path, config, "https://api.example.com", 5)
|
self.match_rule(tmp_path, component_tests_config, "https://api.example.com", 5)
|
||||||
|
|
||||||
|
|
||||||
# This is used to check that the command tunnel ingress url <url> matches rule number <rule_num>. Note that rule number uses 1-based indexing
|
# This is used to check that the command tunnel ingress url <url> matches rule number <rule_num>. Note that rule number uses 1-based indexing
|
||||||
def match_rule(self, tmp_path, config, url, rule_num):
|
def match_rule(self, tmp_path, component_tests_config, url, rule_num):
|
||||||
args = ["ingress", "rule", url]
|
args = ["ingress", "rule", url]
|
||||||
pre_args = ["tunnel"]
|
pre_args = ["tunnel"]
|
||||||
match_rule = start_cloudflared(tmp_path, config, args, pre_args)
|
match_rule = start_cloudflared(tmp_path, component_tests_config, args, pre_args)
|
||||||
|
|
||||||
assert match_rule.returncode == 0, "failed to check rule" + match_rule.stderr.decode("utf-8")
|
assert match_rule.returncode == 0, "failed to check rule" + match_rule.stderr.decode("utf-8")
|
||||||
assert f"Matched rule #{rule_num}" .encode() in match_rule.stdout
|
assert f"Matched rule #{rule_num}" .encode() in match_rule.stdout
|
|
@ -5,11 +5,6 @@ import yaml
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_cloudflared():
|
|
||||||
cfd_binary = os.getenv('CFD_BINARY')
|
|
||||||
return "cloudflared" if cfd_binary is None else cfd_binary
|
|
||||||
|
|
||||||
|
|
||||||
def write_config(path, config):
|
def write_config(path, config):
|
||||||
config_path = path / "config.yaml"
|
config_path = path / "config.yaml"
|
||||||
with open(config_path, 'w') as outfile:
|
with open(config_path, 'w') as outfile:
|
||||||
|
@ -17,11 +12,15 @@ def write_config(path, config):
|
||||||
return config_path
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
def start_cloudflared(path, config, args, pre_args=[]):
|
def start_cloudflared(path, component_test_config, cfd_args, cfd_pre_args=[], classic=False):
|
||||||
|
if classic:
|
||||||
|
config = component_test_config.classic_tunnel_config
|
||||||
|
else:
|
||||||
|
config = component_test_config.named_tunnel_config
|
||||||
config_path = write_config(path, config)
|
config_path = write_config(path, config)
|
||||||
cmd = [get_cloudflared()]
|
cmd = [component_test_config.cloudflared_binary]
|
||||||
cmd += pre_args
|
cmd += cfd_pre_args
|
||||||
cmd += ["--config", config_path]
|
cmd += ["--config", config_path]
|
||||||
cmd += args
|
cmd += cfd_args
|
||||||
LOGGER.info(f"Run cmd {cmd} with config {config}")
|
LOGGER.info(f"Run cmd {cmd} with config {config}")
|
||||||
return subprocess.run(cmd, capture_output=True)
|
return subprocess.run(cmd, capture_output=True)
|
Loading…
Reference in New Issue