diff --git a/BUILD b/BUILD index a54068afed..5328f1b40e 100644 --- a/BUILD +++ b/BUILD @@ -31,9 +31,23 @@ python_requirements( "//:reqs#zake", ] }, + # make sure anything that uses st2-auth-ldap gets the st2auth constant + "st2-auth-ldap": { + "dependencies": [ + "st2auth/st2auth/backends/constants.py", + ] + }, }, ) +target( + name="auth_backends", + dependencies=[ + "//:reqs#st2-auth-backend-flat-file", + "//:reqs#st2-auth-ldap", + ], +) + python_test_utils( name="test_utils", skip_pylint=True, diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b5771cf182..b19d3c0dce 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,7 +14,7 @@ Added working on StackStorm, improve our security posture, and improve CI reliability thanks in part to pants' use of PEX lockfiles. This is not a user-facing addition. #5778 #5789 #5817 #5795 #5830 #5833 #5834 #5841 #5840 #5838 #5842 #5837 #5849 #5850 - #5846 #5853 #5848 #5847 #5858 #5857 + #5846 #5853 #5848 #5847 #5858 #5857 #5860 Contributed by @cognifloyd * Added a joint index to solve the problem of slow mongo queries for scheduled executions. #5805 diff --git a/conf/BUILD b/conf/BUILD index c434d58228..a3374bfca3 100644 --- a/conf/BUILD +++ b/conf/BUILD @@ -3,9 +3,12 @@ file( source="st2rc.sample.ini", ) -file( +sample_conf( # defined in pants-plugins/sample_conf name="st2.conf.sample", source="st2.conf.sample", + dependencies=[ + "tools/config_gen.py", + ], ) file( diff --git a/pants-plugins/README.md b/pants-plugins/README.md index 29289c32ae..d7b5729758 100644 --- a/pants-plugins/README.md +++ b/pants-plugins/README.md @@ -10,6 +10,7 @@ To see available goals, do "./pants help goals" and "./pants help $goal". These StackStorm-specific plugins are probably only useful for the st2 repo. - `api_spec` +- `sample_conf` - `schemas` ### `api_spec` plugin @@ -25,6 +26,15 @@ This plugin also wires up pants so that the `lint` goal runs additional api spec validation on `st2common/st2common/openapi.yaml` with something like `./pants lint st2common/st2common/openapi.yaml`. +### `sample_conf` plugin + +This plugin wires up pants to make sure `conf/st2.conf.sample` gets +regenerated whenever the source files change. Now, whenever someone runs +the `fmt` goal (eg `./pants fmt conf/st2.conf.sample`), the sample will +be regenerated if any of the files used to generate it have changed. +Also, running the `lint` goal will fail if the sample needs to be +regenerated. + ### `schemas` plugin This plugin wires up pants to make sure `contrib/schemas/*.json` gets diff --git a/pants-plugins/sample_conf/BUILD b/pants-plugins/sample_conf/BUILD new file mode 100644 index 0000000000..0eea8b1cf1 --- /dev/null +++ b/pants-plugins/sample_conf/BUILD @@ -0,0 +1,5 @@ +python_sources() + +python_tests( + name="tests", +) diff --git a/pants-plugins/sample_conf/__init__.py b/pants-plugins/sample_conf/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pants-plugins/sample_conf/register.py b/pants-plugins/sample_conf/register.py new file mode 100644 index 0000000000..522f745fb4 --- /dev/null +++ b/pants-plugins/sample_conf/register.py @@ -0,0 +1,24 @@ +# Copyright 2023 The StackStorm Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sample_conf.rules import rules as sample_conf_rules +from sample_conf.target_types import SampleConf + + +def rules(): + return [*sample_conf_rules()] + + +def target_types(): + return [SampleConf] diff --git a/pants-plugins/sample_conf/rules.py b/pants-plugins/sample_conf/rules.py new file mode 100644 index 0000000000..ccc51aac81 --- /dev/null +++ b/pants-plugins/sample_conf/rules.py @@ -0,0 +1,113 @@ +# Copyright 2023 The StackStorm Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from dataclasses import dataclass + +from pants.backend.python.target_types import EntryPoint +from pants.backend.python.util_rules import pex, pex_from_targets +from pants.backend.python.util_rules.pex import ( + VenvPex, + VenvPexProcess, +) +from pants.backend.python.util_rules.pex_from_targets import PexFromTargetsRequest +from pants.core.goals.fmt import FmtResult, FmtTargetsRequest +from pants.engine.addresses import Address +from pants.engine.fs import ( + CreateDigest, + Digest, + FileContent, + Snapshot, +) +from pants.engine.process import FallibleProcessResult +from pants.engine.rules import Get, collect_rules, rule +from pants.engine.target import FieldSet +from pants.engine.unions import UnionRule +from pants.util.logging import LogLevel + +from sample_conf.target_types import SampleConfSourceField + + +# these constants are also used in the tests. +SCRIPT_DIR = "tools" +SCRIPT = "config_gen" + + +@dataclass(frozen=True) +class GenerateSampleConfFieldSet(FieldSet): + required_fields = (SampleConfSourceField,) + + source: SampleConfSourceField + + +class GenerateSampleConfViaFmtTargetsRequest(FmtTargetsRequest): + field_set_type = GenerateSampleConfFieldSet + name = SCRIPT + + +@rule( + desc=f"Update conf/st2.conf.sample with {SCRIPT_DIR}/{SCRIPT}.py", + level=LogLevel.DEBUG, +) +async def generate_sample_conf_via_fmt( + request: GenerateSampleConfViaFmtTargetsRequest, +) -> FmtResult: + # There will only be one target+field_set, but we iterate + # to satisfy how fmt expects that there could be more than one. + # If there is more than one, they will all get the same contents. + + # actually generate it with an external script. + # Generation cannot be inlined here because it needs to import the st2 code. + pex = await Get( + VenvPex, + PexFromTargetsRequest( + [ + Address( + SCRIPT_DIR, + target_name=SCRIPT_DIR, + relative_file_path=f"{SCRIPT}.py", + ) + ], + output_filename=f"{SCRIPT}.pex", + internal_only=True, + main=EntryPoint(SCRIPT), + ), + ) + + result = await Get( + FallibleProcessResult, + VenvPexProcess( + pex, + description="Regenerating st2.conf.sample", + ), + ) + + contents = [ + FileContent( + f"{field_set.address.spec_path}/{field_set.source.value}", + result.stdout, + ) + for field_set in request.field_sets + ] + + output_digest = await Get(Digest, CreateDigest(contents)) + output_snapshot = await Get(Snapshot, Digest, output_digest) + return FmtResult.create(request, result, output_snapshot, strip_chroot_path=True) + + +def rules(): + return [ + *collect_rules(), + UnionRule(FmtTargetsRequest, GenerateSampleConfViaFmtTargetsRequest), + *pex.rules(), + *pex_from_targets.rules(), + ] diff --git a/pants-plugins/sample_conf/rules_test.py b/pants-plugins/sample_conf/rules_test.py new file mode 100644 index 0000000000..4986e7b905 --- /dev/null +++ b/pants-plugins/sample_conf/rules_test.py @@ -0,0 +1,158 @@ +# Copyright 2023 The StackStorm Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +import pytest + +from pants.backend.python import target_types_rules +from pants.backend.python.target_types import PythonSourcesGeneratorTarget + +from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest +from pants.engine.addresses import Address +from pants.engine.fs import CreateDigest, Digest, FileContent, Snapshot +from pants.engine.target import Target +from pants.core.goals.fmt import FmtResult +from pants.testutil.rule_runner import QueryRule, RuleRunner + +from .rules import ( + SCRIPT, + SCRIPT_DIR, + GenerateSampleConfFieldSet, + GenerateSampleConfViaFmtTargetsRequest, + rules as sample_conf_rules, +) +from .target_types import SampleConf + + +@pytest.fixture +def rule_runner() -> RuleRunner: + return RuleRunner( + rules=[ + *sample_conf_rules(), + *target_types_rules.rules(), + QueryRule(FmtResult, (GenerateSampleConfViaFmtTargetsRequest,)), + QueryRule(SourceFiles, (SourceFilesRequest,)), + ], + target_types=[SampleConf, PythonSourcesGeneratorTarget], + ) + + +def run_st2_generate_sample_conf( + rule_runner: RuleRunner, + targets: list[Target], + *, + extra_args: list[str] | None = None, +) -> FmtResult: + rule_runner.set_options( + [ + "--backend-packages=sample_conf", + f"--source-root-patterns=/{SCRIPT_DIR}", + *(extra_args or ()), + ], + env_inherit={"PATH", "PYENV_ROOT", "HOME"}, + ) + field_sets = [GenerateSampleConfFieldSet.create(tgt) for tgt in targets] + input_sources = rule_runner.request( + SourceFiles, + [ + SourceFilesRequest(field_set.source for field_set in field_sets), + ], + ) + fmt_result = rule_runner.request( + FmtResult, + [ + GenerateSampleConfViaFmtTargetsRequest( + field_sets, snapshot=input_sources.snapshot + ), + ], + ) + return fmt_result + + +# copied from pantsbuild/pants.git/src/python/pants/backend/python/lint/black/rules_integration_test.py +def get_snapshot(rule_runner: RuleRunner, source_files: dict[str, str]) -> Snapshot: + files = [ + FileContent(path, content.encode()) for path, content in source_files.items() + ] + digest = rule_runner.request(Digest, [CreateDigest(files)]) + return rule_runner.request(Snapshot, [digest]) + + +# add dummy script at tools/config_gen.py that the test can load. +SCRIPT_PY = """ +def main(): + sample_conf_text = "{sample_conf_text}" + print(sample_conf_text) + + +if __name__ == "__main__": + main() +""" + + +def write_files( + sample_conf_dir: str, + sample_conf_file: str, + before: str, + after: str, + rule_runner: RuleRunner, +) -> None: + files = { + f"{sample_conf_dir}/{sample_conf_file}": before, + f"{sample_conf_dir}/BUILD": f"sample_conf(name='t', source='{sample_conf_file}')", + # add in the target that's hard-coded in the generate_sample_conf_via_fmt rue + f"{SCRIPT_DIR}/{SCRIPT}.py": SCRIPT_PY.format(sample_conf_text=after), + f"{SCRIPT_DIR}/__init__.py": "", + f"{SCRIPT_DIR}/BUILD": "python_sources()", + } + + rule_runner.write_files(files) + + +def test_changed(rule_runner: RuleRunner) -> None: + write_files( + sample_conf_dir="my_dir", + sample_conf_file="dummy.conf", + before="BEFORE", + after="AFTER", + rule_runner=rule_runner, + ) + + tgt = rule_runner.get_target( + Address("my_dir", target_name="t", relative_file_path="dummy.conf") + ) + fmt_result = run_st2_generate_sample_conf(rule_runner, [tgt]) + assert fmt_result.output == get_snapshot( + rule_runner, {"my_dir/dummy.conf": "AFTER\n"} + ) + assert fmt_result.did_change is True + + +def test_unchanged(rule_runner: RuleRunner) -> None: + write_files( + sample_conf_dir="my_dir", + sample_conf_file="dummy.conf", + before="AFTER\n", + after="AFTER", # print() adds a newline + rule_runner=rule_runner, + ) + + tgt = rule_runner.get_target( + Address("my_dir", target_name="t", relative_file_path="dummy.conf") + ) + fmt_result = run_st2_generate_sample_conf(rule_runner, [tgt]) + assert fmt_result.output == get_snapshot( + rule_runner, {"my_dir/dummy.conf": "AFTER\n"} + ) + assert fmt_result.did_change is False diff --git a/pants-plugins/sample_conf/target_types.py b/pants-plugins/sample_conf/target_types.py new file mode 100644 index 0000000000..b7f13e12b6 --- /dev/null +++ b/pants-plugins/sample_conf/target_types.py @@ -0,0 +1,29 @@ +# Copyright 2023 The StackStorm Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from pants.engine.target import ( + COMMON_TARGET_FIELDS, + Dependencies, + SingleSourceField, + Target, +) + + +class SampleConfSourceField(SingleSourceField): + default = "st2.conf.sample" + + +class SampleConf(Target): + alias = "sample_conf" + core_fields = (*COMMON_TARGET_FIELDS, Dependencies, SampleConfSourceField) + help = "Generate st2.conf.sample file from python sources." diff --git a/pants.toml b/pants.toml index 922e7984d9..15a057cf02 100644 --- a/pants.toml +++ b/pants.toml @@ -25,6 +25,7 @@ backend_packages = [ # internal plugins in pants-plugins/ "pants.backend.plugin_development", "api_spec", + "sample_conf", "schemas", ] # pants ignores files in .gitignore, .*/ directories, /dist/ directory, and __pycache__. diff --git a/tools/BUILD b/tools/BUILD index 17e8df14c8..a9a7a9e3c7 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -1,4 +1,27 @@ -python_sources() +python_sources( + overrides={ + "config_gen.py": { + "dependencies": [ + # the auth backends get listed in the conf file + "//:auth_backends", + # the following should match CONFIGS in config_gen.py + # grep -rl '^def register_opts(ignore_errors=False):' st2* + "st2actions/st2actions/scheduler/config.py", + "st2actions/st2actions/workflows/config.py", + "st2actions/st2actions/notifier/config.py", + "st2actions/st2actions/config.py", + "st2api/st2api/config.py", + "st2auth/st2auth/config.py", + "st2common/st2common/config.py", + "st2reactor/st2reactor/garbage_collector/config.py", + "st2reactor/st2reactor/timer/config.py", + "st2reactor/st2reactor/sensor/config.py", + "st2reactor/st2reactor/rules/config.py", + "st2stream/st2stream/config.py", + ] + }, + }, +) shell_sources( name="shell", diff --git a/tools/config_gen.py b/tools/config_gen.py index aeba38ff0e..92d1093eab 100755 --- a/tools/config_gen.py +++ b/tools/config_gen.py @@ -25,6 +25,10 @@ CONFIGS = [ + # this is duplicated in tools/BUILD + # TODO: replace this with a heuristic that searches for config.py + # maybe with an exclude list (eg st2tests.config st2client) + # grep -rl 'def register_opts(ignore_errors=False):' st2* "st2actions.config", "st2actions.scheduler.config", "st2actions.notifier.config",