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:
|
||||
- sudo pip3 install --upgrade -r component-tests/requirements.txt
|
||||
post-cache:
|
||||
# Constructs config file from env vars
|
||||
- python3 component-tests/config.py
|
||||
- pytest component-tests
|
||||
update-homebrew:
|
||||
builddeps:
|
||||
|
|
|
@ -5,7 +5,17 @@
|
|||
- `conda activate component-tests`
|
||||
- `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
|
||||
Specify path to config file via env var `COMPONENT_TESTS_CONFIG`. This is required.
|
||||
## All tests
|
||||
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:
|
||||
# tmp_path is a fixture provides a temporary directory unique to the test invocation
|
||||
def test_validate_ingress_rules(self, tmp_path):
|
||||
config = {
|
||||
'metrics': 'localhost:50000',
|
||||
def test_validate_ingress_rules(self, tmp_path, component_tests_config):
|
||||
extra_config = {
|
||||
'ingress': [
|
||||
{
|
||||
"hostname": "example.com",
|
||||
|
@ -31,23 +30,25 @@ class TestConfig:
|
|||
{"service": "http_status:404"}
|
||||
],
|
||||
}
|
||||
component_tests_config = component_tests_config(extra_config)
|
||||
validate_args = ["ingress", "validate"]
|
||||
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")
|
||||
|
||||
self.match_rule(tmp_path, config, "http://example.com/index.html", 1)
|
||||
self.match_rule(tmp_path, config, "https://example.com/index.html", 1)
|
||||
self.match_rule(tmp_path, config, "https://api.example.com/login", 2)
|
||||
self.match_rule(tmp_path, config, "https://wss.example.com", 3)
|
||||
self.match_rule(tmp_path, 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, "http://example.com/index.html", 1)
|
||||
self.match_rule(tmp_path, component_tests_config, "https://example.com/index.html", 1)
|
||||
self.match_rule(tmp_path, component_tests_config, "https://api.example.com/login", 2)
|
||||
self.match_rule(tmp_path, component_tests_config, "https://wss.example.com", 3)
|
||||
self.match_rule(tmp_path, component_tests_config, "https://ssh.example.com", 4)
|
||||
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
|
||||
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]
|
||||
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 f"Matched rule #{rule_num}" .encode() in match_rule.stdout
|
|
@ -5,11 +5,6 @@ import yaml
|
|||
|
||||
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):
|
||||
config_path = path / "config.yaml"
|
||||
with open(config_path, 'w') as outfile:
|
||||
|
@ -17,11 +12,15 @@ def write_config(path, config):
|
|||
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)
|
||||
cmd = [get_cloudflared()]
|
||||
cmd += pre_args
|
||||
cmd = [component_test_config.cloudflared_binary]
|
||||
cmd += cfd_pre_args
|
||||
cmd += ["--config", config_path]
|
||||
cmd += args
|
||||
cmd += cfd_args
|
||||
LOGGER.info(f"Run cmd {cmd} with config {config}")
|
||||
return subprocess.run(cmd, capture_output=True)
|
Loading…
Reference in New Issue