Skip to content

Commit 8603726

Browse files
CDRIVER-4730 TLS Backend Cleanup (#1408)
* Clean up of TLS detection and import * Earthly TLS controls and cleaner build envs Adds a --tls build parameter to switch TLS backends. This PR also adds a "str" utility for doing more concise string manipulation in POSIX sh. * EVG Earthly tasks for more build configurations This refactors earthly.py for extensibility and to match the parameterization used in Earthfile. * Support "auto" in Earthfile * Tweak name spacing * More feature and package summary information * Unused LibreSSL compat
1 parent 905c71f commit 8603726

File tree

9 files changed

+1065
-296
lines changed

9 files changed

+1065
-296
lines changed
Lines changed: 158 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,160 @@
1-
from typing import Iterable
1+
from __future__ import annotations
2+
3+
import functools
4+
import itertools
5+
from typing import Any, Iterable, Literal, TypeVar, get_args, NamedTuple, get_type_hints
26
from shrub.v3.evg_build_variant import BuildVariant
37
from shrub.v3.evg_task import EvgTaskRef
48
from ..etc.utils import Task
59
from shrub.v3.evg_command import subprocess_exec, EvgCommandType
610

7-
_ENV_PARAM_NAME = "MONGOC_EARTHLY_ENV"
8-
11+
T = TypeVar("T")
912

10-
class EarthlyTask(Task):
11-
def __init__(self, *, suffix: str, target: str, sasl: str) -> None:
12-
super().__init__(
13-
name=f"earthly-{suffix}",
14-
commands=[
15-
subprocess_exec(
16-
"bash",
17-
args=[
18-
"tools/earthly.sh",
19-
f"+{target}",
20-
f"--env=${{{_ENV_PARAM_NAME}}}",
21-
f"--enable_sasl={sasl}",
22-
],
23-
working_dir="mongoc",
24-
command_type=EvgCommandType.TEST,
25-
)
26-
],
27-
tags=[f"earthly", "pr-merge-gate"],
28-
run_on=CONTAINER_RUN_DISTROS,
29-
)
13+
_ENV_PARAM_NAME = "MONGOC_EARTHLY_ENV"
14+
"The name of the EVG expansion parameter used to key the Earthly build env"
3015

16+
EnvKey = Literal["u22", "alpine3.18", "archlinux"]
17+
"Identifiers for environments. These correspond to special '*-env' targets in the Earthfile."
3118

32-
#: A mapping from environment keys to the environment name.
33-
#: These correspond to special "*-env" targets in the Earthfile.
34-
ENVS = {
19+
_ENV_NAMES: dict[EnvKey, str] = {
3520
"u22": "Ubuntu 22.04",
3621
"alpine3.18": "Alpine 3.18",
3722
"archlinux": "Arch Linux",
3823
}
24+
"A mapping from environment keys to 'pretty' environment names"
25+
26+
# Other options: SSPI (Windows only), AUTO (not reliably test-able without more environments)
27+
SASLOption = Literal["Cyrus", "off"]
28+
"Valid options for the SASL configuration parameter"
29+
TLSOption = Literal["LibreSSL", "OpenSSL", "off"]
30+
"Options for the TLS backend configuration parameter (AKA 'ENABLE_SSL')"
31+
CxxVersion = Literal["master", "r3.8.0"]
32+
"C++ driver refs that are under CI test"
33+
34+
# A Unicode non-breaking space character
35+
_BULLET = "\N{Bullet}"
36+
37+
38+
class Configuration(NamedTuple):
39+
"""
40+
Represent a complete set of argument values to give to the Earthly task
41+
execution. Each field name matches the ARG in the Earthfile.
42+
43+
Adding/removing fields will add/remove dimensions on the task matrix.
44+
45+
The 'env' parameter is not encoded here, but is managed separately.
46+
47+
Keep this in sync with the 'PartialConfiguration' class defined below!
48+
"""
49+
50+
sasl: SASLOption
51+
tls: TLSOption
52+
test_mongocxx_ref: CxxVersion
53+
54+
@classmethod
55+
def all(cls) -> Iterable[Configuration]:
56+
"""
57+
Generate all configurations for all options of our parameters.
58+
"""
59+
# Iter each configuration parameter:
60+
fields: Iterable[tuple[str, type]] = get_type_hints(Configuration).items()
61+
# Generate lists of pairs of parameter names their options:
62+
all_pairs: Iterable[Iterable[tuple[str, str]]] = (
63+
# Generate a (key, opt) pair for each option in parameter 'key'
64+
[(key, opt) for opt in get_args(typ)]
65+
# Over each parameter and type thereof:
66+
for key, typ in fields
67+
)
68+
# Now generate the cross product of all alternative for all options:
69+
matrix: Iterable[dict[str, Any]] = map(dict, itertools.product(*all_pairs))
70+
for items in matrix:
71+
# Convert each item to a Configuration:
72+
yield Configuration(**items)
73+
74+
@property
75+
def suffix(self) -> str:
76+
return f"{_BULLET}".join(f"{k}={v}" for k, v in self._asdict().items())
77+
78+
79+
def task_filter(env: EnvKey, conf: Configuration) -> bool:
80+
"""
81+
Control which tasks are actually defined by matching on the platform and
82+
configuration values.
83+
"""
84+
match env, conf:
85+
# We only need one task with "sasl=off"
86+
case "u22", ("off", "OpenSSL", "master"):
87+
return True
88+
# Other sasl=off tasks we'll just ignore:
89+
case _, ("off", _tls, _cxx):
90+
return False
91+
# Ubuntu does not ship with a LibreSSL package:
92+
case e, (_sasl, "LibreSSL", _cxx) if _ENV_NAMES[e].startswith("Ubuntu"):
93+
return False
94+
# Anything else: Allow it to run:
95+
case _:
96+
return True
97+
98+
99+
def envs_for(config: Configuration) -> Iterable[EnvKey]:
100+
"""Get all environment keys that are not excluded for the given configuration"""
101+
all_envs: tuple[EnvKey, ...] = get_args(EnvKey)
102+
allow_env_for_config = functools.partial(task_filter, conf=config)
103+
return filter(allow_env_for_config, all_envs)
104+
105+
106+
def earthly_task(
107+
*,
108+
name: str,
109+
targets: Iterable[str],
110+
config: Configuration,
111+
) -> Task | None:
112+
# Attach "earthly-xyz" tags to the task to allow build variants to select
113+
# these tasks by the environment of that variant.
114+
env_tags = sorted(f"earthly-{e}" for e in sorted(envs_for(config)))
115+
if not env_tags:
116+
# All environments have been excluded for this configuration. This means
117+
# the task itself should not be run:
118+
return
119+
# Generate the build-arg arguments based on the configuration options. The
120+
# NamedTuple field names must match with the ARG keys in the Earthfile!
121+
earthly_args = [f"--{key}={val}" for key, val in config._asdict().items()]
122+
return Task(
123+
name=name,
124+
commands=[
125+
# First, just build the "env-warmup" which will prepare the build environment.
126+
# This won't generate any output, but allows EVG to track it as a separate build step
127+
# for timing and logging purposes. The subequent build step will cache-hit the
128+
# warmed-up build environments.
129+
subprocess_exec(
130+
"bash",
131+
args=[
132+
"tools/earthly.sh",
133+
"+env-warmup",
134+
f"--env=${{{_ENV_PARAM_NAME}}}",
135+
*earthly_args,
136+
],
137+
working_dir="mongoc",
138+
command_type=EvgCommandType.SETUP,
139+
),
140+
# Now execute the main tasks:
141+
subprocess_exec(
142+
"bash",
143+
args=[
144+
"tools/earthly.sh",
145+
"+run",
146+
f"--targets={' '.join(targets)}",
147+
f"--env=${{{_ENV_PARAM_NAME}}}",
148+
*earthly_args,
149+
],
150+
working_dir="mongoc",
151+
command_type=EvgCommandType.TEST,
152+
),
153+
],
154+
tags=[f"earthly", "pr-merge-gate", *env_tags],
155+
run_on=CONTAINER_RUN_DISTROS,
156+
)
157+
39158

40159
CONTAINER_RUN_DISTROS = [
41160
"ubuntu2204-small",
@@ -49,28 +168,28 @@ def __init__(self, *, suffix: str, target: str, sasl: str) -> None:
49168
"amazon2",
50169
]
51170

52-
# Other options: SSPI (Windows only), AUTO (not reliably test-able without more environments)
53-
_SASL_OPTIONS = ["CYRUS", "OFF"]
54-
55171

56172
def tasks() -> Iterable[Task]:
57-
for sasl in _SASL_OPTIONS:
58-
yield EarthlyTask(
59-
suffix=f"build-check-sasl:{sasl}".lower(),
60-
target="test-example",
61-
sasl=sasl,
173+
for conf in Configuration.all():
174+
task = earthly_task(
175+
name=f"check:{conf.suffix}",
176+
targets=("test-example", "test-cxx-driver"),
177+
config=conf,
62178
)
179+
if task is not None:
180+
yield task
63181

64182

65183
def variants() -> list[BuildVariant]:
184+
envs: tuple[EnvKey, ...] = get_args(EnvKey)
66185
return [
67186
BuildVariant(
68-
name=f"earthly-{env_key}",
69-
tasks=[EvgTaskRef(name=".earthly")],
70-
display_name=env_name,
187+
name=f"earthly-{env}",
188+
tasks=[EvgTaskRef(name=f".earthly-{env}")],
189+
display_name=_ENV_NAMES[env],
71190
expansions={
72-
_ENV_PARAM_NAME: env_key,
191+
_ENV_PARAM_NAME: env,
73192
},
74193
)
75-
for env_key, env_name in ENVS.items()
194+
for env in envs
76195
]

0 commit comments

Comments
 (0)