Skip to content

Commit 1413e11

Browse files
committed
pants-plugins/uses_services: add detection for system_user
1 parent d86a31c commit 1413e11

10 files changed

Lines changed: 342 additions & 2 deletions

File tree

contrib/runners/orquesta_runner/tests/unit/BUILD

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ __defaults__(
55

66
python_tests(
77
name="tests",
8+
overrides={
9+
(
10+
"test_basic.py",
11+
"test_cancel.py",
12+
"test_context.py",
13+
"test_error_handling.py",
14+
"test_pause_and_resume.py",
15+
"test_with_items.py",
16+
): dict(
17+
uses=["system_user"],
18+
),
19+
},
820
)
921

1022
python_test_utils(

pants-plugins/uses_services/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ python_tests(
1717
# "mongo_rules_test.py": dict(uses=["mongo"]),
1818
# "rabbitmq_rules_test.py": dict(uses=["rabbitmq"]),
1919
# "redis_rules_test.py": dict(uses=["redis"]),
20+
# "system_user_test.py": dict(uses=["system_user"]),
2021
# },
2122
)

pants-plugins/uses_services/register.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616
PythonTestsGeneratorTarget,
1717
)
1818

19-
from uses_services import mongo_rules, platform_rules, rabbitmq_rules, redis_rules
19+
from uses_services import (
20+
mongo_rules,
21+
platform_rules,
22+
rabbitmq_rules,
23+
redis_rules,
24+
system_user_rules,
25+
)
2026
from uses_services.target_types import UsesServicesField
2127

2228

@@ -28,4 +34,5 @@ def rules():
2834
*mongo_rules.rules(),
2935
*rabbitmq_rules.rules(),
3036
*redis_rules.rules(),
37+
*system_user_rules.rules(),
3138
]
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2023 The StackStorm Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import annotations
15+
16+
import os
17+
import pwd
18+
import sys
19+
20+
21+
def _has_system_user(system_user: str) -> bool:
22+
"""Make sure the system_user exists.
23+
24+
This should not import the st2 code as it should be self-contained.
25+
"""
26+
try:
27+
pwd.getpwnam(system_user)
28+
except KeyError:
29+
# put current user (for use in error msg instructions)
30+
print(pwd.getpwuid(os.getuid()).pw_name)
31+
return False
32+
print(system_user)
33+
return True
34+
35+
36+
if __name__ == "__main__":
37+
args = dict((k, v) for k, v in enumerate(sys.argv))
38+
39+
system_user = args.get(1, "stanley")
40+
41+
is_running = _has_system_user(system_user)
42+
exit_code = 0 if is_running else 1
43+
sys.exit(exit_code)
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Copyright 2023 The StackStorm Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import annotations
15+
16+
from dataclasses import dataclass
17+
from textwrap import dedent
18+
19+
from pants.backend.python.goals.pytest_runner import (
20+
PytestPluginSetupRequest,
21+
PytestPluginSetup,
22+
)
23+
from pants.backend.python.target_types import Executable
24+
from pants.backend.python.util_rules.pex import (
25+
PexRequest,
26+
VenvPex,
27+
VenvPexProcess,
28+
rules as pex_rules,
29+
)
30+
from pants.core.goals.test import TestExtraEnv
31+
from pants.engine.fs import CreateDigest, Digest, FileContent
32+
from pants.engine.rules import collect_rules, Get, rule
33+
from pants.engine.process import FallibleProcessResult, ProcessCacheScope
34+
from pants.engine.target import Target
35+
from pants.engine.unions import UnionRule
36+
from pants.util.logging import LogLevel
37+
38+
from uses_services.exceptions import ServiceMissingError
39+
from uses_services.platform_rules import Platform
40+
from uses_services.scripts.has_system_user import (
41+
__file__ as has_system_user_full_path,
42+
)
43+
from uses_services.target_types import UsesServicesField
44+
45+
46+
@dataclass(frozen=True)
47+
class UsesSystemUserRequest:
48+
"""One or more targets need the system_user (like stanley) using these settings.
49+
50+
The system_user attributes represent the system_user.user settings from st2.conf.
51+
In st2 code, they come from:
52+
oslo_config.cfg.CONF.system_user.user
53+
"""
54+
55+
system_user: str = "stanley"
56+
57+
58+
@dataclass(frozen=True)
59+
class HasSystemUser:
60+
pass
61+
62+
63+
class PytestUsesSystemUserRequest(PytestPluginSetupRequest):
64+
@classmethod
65+
def is_applicable(cls, target: Target) -> bool:
66+
if not target.has_field(UsesServicesField):
67+
return False
68+
uses = target.get(UsesServicesField).value
69+
return uses is not None and "system_user" in uses
70+
71+
72+
@rule(
73+
desc="Ensure system_user is present before running tests.",
74+
level=LogLevel.DEBUG,
75+
)
76+
async def has_system_user_for_pytest(
77+
request: PytestUsesSystemUserRequest,
78+
test_extra_env: TestExtraEnv,
79+
) -> PytestPluginSetup:
80+
system_user = test_extra_env.env.get("ST2TESTS_SYSTEM_USER", "stanley")
81+
82+
# this will raise an error if system_user is not present
83+
_ = await Get(HasSystemUser, UsesSystemUserRequest(system_user=system_user))
84+
85+
return PytestPluginSetup()
86+
87+
88+
@rule(
89+
desc="Test to see if system_user is present.",
90+
level=LogLevel.DEBUG,
91+
)
92+
async def has_system_user(
93+
request: UsesSystemUserRequest, platform: Platform
94+
) -> HasSystemUser:
95+
script_path = "./has_system_user.py"
96+
97+
# pants is already watching this directory as it is under a source root.
98+
# So, we don't need to double watch with PathGlobs, just open it.
99+
with open(has_system_user_full_path, "rb") as script_file:
100+
script_contents = script_file.read()
101+
102+
script_digest = await Get(
103+
Digest, CreateDigest([FileContent(script_path, script_contents)])
104+
)
105+
script_pex = await Get(
106+
VenvPex,
107+
PexRequest(
108+
output_filename="script.pex",
109+
internal_only=True,
110+
sources=script_digest,
111+
main=Executable(script_path),
112+
),
113+
)
114+
115+
result = await Get(
116+
FallibleProcessResult,
117+
VenvPexProcess(
118+
script_pex,
119+
argv=(request.system_user,),
120+
description="Checking to see if system_user is present.",
121+
# this can change from run to run, so don't cache results.
122+
cache_scope=ProcessCacheScope.PER_SESSION,
123+
level=LogLevel.DEBUG,
124+
),
125+
)
126+
has_user = result.exit_code == 0
127+
128+
if has_user:
129+
return HasSystemUser()
130+
131+
current_user = result.stdout.decode().strip()
132+
133+
# system_user is not present, so raise an error with instructions.
134+
raise ServiceMissingError(
135+
service="system_user",
136+
platform=platform,
137+
msg=dedent(
138+
f"""\
139+
The system_user ({request.system_user}) does not seem to be present!
140+
141+
Please export the ST2TESTS_SYSTEM_USER env var to specify which user
142+
tests should use as the system_user. This user must be present on
143+
your system.
144+
145+
To use your current user ({current_user}) as the system_user, run:
146+
147+
export ST2TESTS_SYSTEM_USER=$(id -un)
148+
"""
149+
),
150+
)
151+
152+
153+
def rules():
154+
return [
155+
*collect_rules(),
156+
UnionRule(PytestPluginSetupRequest, PytestUsesSystemUserRequest),
157+
*pex_rules(),
158+
]
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright 2023 The StackStorm Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import annotations
15+
16+
import os
17+
18+
import pytest
19+
20+
from pants.engine.internals.scheduler import ExecutionError
21+
from pants.testutil.rule_runner import QueryRule, RuleRunner
22+
23+
from .data_fixtures import platform, platform_samples
24+
from .exceptions import ServiceMissingError
25+
from .system_user_rules import (
26+
HasSystemUser,
27+
UsesSystemUserRequest,
28+
rules as system_user_rules,
29+
)
30+
from .platform_rules import Platform
31+
32+
33+
@pytest.fixture
34+
def rule_runner() -> RuleRunner:
35+
return RuleRunner(
36+
rules=[
37+
*system_user_rules(),
38+
QueryRule(HasSystemUser, (UsesSystemUserRequest, Platform)),
39+
],
40+
target_types=[],
41+
)
42+
43+
44+
def run_has_system_user(
45+
rule_runner: RuleRunner,
46+
uses_system_user_request: UsesSystemUserRequest,
47+
mock_platform: Platform,
48+
*,
49+
extra_args: list[str] | None = None,
50+
) -> HasSystemUser:
51+
rule_runner.set_options(
52+
[
53+
"--backend-packages=uses_services",
54+
*(extra_args or ()),
55+
],
56+
env_inherit={"PATH", "PYENV_ROOT", "HOME", "ST2TESTS_SYSTEM_USER"},
57+
)
58+
result = rule_runner.request(
59+
HasSystemUser,
60+
[uses_system_user_request, mock_platform],
61+
)
62+
return result
63+
64+
65+
# Warning this requires that system_user is present
66+
def test_system_user_is_present(rule_runner: RuleRunner) -> None:
67+
request = UsesSystemUserRequest(
68+
system_user=os.environ.get("ST2TESTS_SYSTEM_USER", "stanley")
69+
)
70+
mock_platform = platform(os="TestMock")
71+
72+
# we are asserting that this does not raise an exception
73+
has_user = run_has_system_user(rule_runner, request, mock_platform)
74+
assert has_user
75+
76+
77+
@pytest.mark.parametrize("mock_platform", platform_samples)
78+
def test_system_user_is_absent(
79+
rule_runner: RuleRunner, mock_platform: Platform
80+
) -> None:
81+
request = UsesSystemUserRequest(
82+
system_user="bogus-stanley",
83+
)
84+
85+
with pytest.raises(ExecutionError) as exception_info:
86+
run_has_system_user(rule_runner, request, mock_platform)
87+
88+
execution_error = exception_info.value
89+
assert len(execution_error.wrapped_exceptions) == 1
90+
91+
exc = execution_error.wrapped_exceptions[0]
92+
assert isinstance(exc, ServiceMissingError)
93+
94+
assert exc.service == "system_user"
95+
assert "The system_user (bogus-stanley) does not seem to be present" in str(exc)
96+
assert not exc.instructions

pants-plugins/uses_services/target_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from pants.engine.target import StringSequenceField
1515

1616

17-
supported_services = ("mongo", "rabbitmq", "redis")
17+
supported_services = ("mongo", "rabbitmq", "redis", "system_user")
1818

1919

2020
class UsesServicesField(StringSequenceField):

st2actions/tests/unit/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,13 @@ __defaults__(
55

66
python_tests(
77
name="tests",
8+
overrides={
9+
(
10+
"test_execution_cancellation.py",
11+
"test_runner_container.py",
12+
"test_worker.py",
13+
): dict(
14+
uses=["system_user"],
15+
),
16+
},
817
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
python_tests(
22
name="tests",
3+
overrides={
4+
(
5+
"test_alias_execution.py",
6+
"test_auth.py",
7+
"test_auth_api_keys.py",
8+
"test_executions.py",
9+
"test_inquiries.py",
10+
): dict(
11+
uses=["system_user"],
12+
),
13+
},
314
)

st2common/tests/unit/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ python_tests(
1919
"st2tests/st2tests/policies/meta",
2020
],
2121
),
22+
"test_param_utils.py": dict(
23+
uses=["system_user"],
24+
),
2225
},
2326
)
2427

0 commit comments

Comments
 (0)