qontract-reconcile 0.10.1rc1201__py3-none-any.whl → 0.10.2.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qontract_reconcile-0.10.2.dev1.dist-info/METADATA +500 -0
- {qontract_reconcile-0.10.1rc1201.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/RECORD +14 -132
- {qontract_reconcile-0.10.1rc1201.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/WHEEL +1 -2
- {qontract_reconcile-0.10.1rc1201.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/entry_points.txt +1 -0
- reconcile/aws_account_manager/README.md +5 -0
- reconcile/change_owners/README.md +34 -0
- reconcile/external_resources/manager.py +12 -1
- reconcile/external_resources/model.py +11 -0
- reconcile/glitchtip/README.md +150 -0
- reconcile/gql_definitions/introspection.json +51176 -0
- reconcile/run_integration.py +293 -0
- reconcile/utils/binary.py +2 -2
- reconcile/utils/mr/README.md +198 -0
- reconcile/utils/oc_map.py +2 -2
- tools/qontract_cli.py +0 -0
- qontract_reconcile-0.10.1rc1201.dist-info/METADATA +0 -64
- qontract_reconcile-0.10.1rc1201.dist-info/top_level.txt +0 -3
- reconcile/test/__init__.py +0 -0
- reconcile/test/conftest.py +0 -157
- reconcile/test/fixtures.py +0 -24
- reconcile/test/saas_auto_promotions_manager/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/conftest.py +0 -170
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +0 -115
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +0 -19
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_desired_state.py +0 -66
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +0 -86
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py +0 -352
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py +0 -494
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +0 -25
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py +0 -37
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_namespace.py +0 -81
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_target.py +0 -61
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_json_path_selector.py +0 -74
- reconcile/test/saas_auto_promotions_manager/test_integration_test.py +0 -52
- reconcile/test/saas_auto_promotions_manager/utils/__init__.py +0 -0
- reconcile/test/test_acs_notifiers.py +0 -393
- reconcile/test/test_acs_policies.py +0 -497
- reconcile/test/test_acs_rbac.py +0 -865
- reconcile/test/test_aggregated_list.py +0 -237
- reconcile/test/test_amtool.py +0 -37
- reconcile/test/test_aws_ami_cleanup.py +0 -230
- reconcile/test/test_aws_ami_share.py +0 -68
- reconcile/test/test_aws_cloudwatch_log_retention.py +0 -434
- reconcile/test/test_aws_iam_keys.py +0 -70
- reconcile/test/test_aws_iam_password_reset.py +0 -35
- reconcile/test/test_aws_support_cases_sos.py +0 -23
- reconcile/test/test_checkpoint.py +0 -178
- reconcile/test/test_cli.py +0 -41
- reconcile/test/test_closedbox_endpoint_monitoring.py +0 -207
- reconcile/test/test_dashdotdb_dora.py +0 -245
- reconcile/test/test_database_access_manager.py +0 -660
- reconcile/test/test_deadmanssnitch.py +0 -290
- reconcile/test/test_gabi_authorized_users.py +0 -72
- reconcile/test/test_gcr_mirror.py +0 -14
- reconcile/test/test_github_org.py +0 -156
- reconcile/test/test_github_repo_invites.py +0 -119
- reconcile/test/test_gitlab_housekeeping.py +0 -333
- reconcile/test/test_gitlab_labeler.py +0 -126
- reconcile/test/test_gitlab_members.py +0 -219
- reconcile/test/test_gitlab_permissions.py +0 -164
- reconcile/test/test_instrumented_wrappers.py +0 -18
- reconcile/test/test_integrations_manager.py +0 -1252
- reconcile/test/test_jenkins_worker_fleets.py +0 -57
- reconcile/test/test_jira_permissions_validator.py +0 -519
- reconcile/test/test_jump_host.py +0 -114
- reconcile/test/test_ldap_users.py +0 -125
- reconcile/test/test_make.py +0 -28
- reconcile/test/test_ocm_additional_routers.py +0 -133
- reconcile/test/test_ocm_clusters.py +0 -798
- reconcile/test/test_ocm_clusters_manifest_updates.py +0 -87
- reconcile/test/test_ocm_machine_pools.py +0 -1103
- reconcile/test/test_ocm_update_recommended_version.py +0 -145
- reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +0 -125
- reconcile/test/test_openshift_base.py +0 -1269
- reconcile/test/test_openshift_cluster_bots.py +0 -240
- reconcile/test/test_openshift_namespace_labels.py +0 -344
- reconcile/test/test_openshift_namespaces.py +0 -256
- reconcile/test/test_openshift_resource.py +0 -443
- reconcile/test/test_openshift_resources_base.py +0 -478
- reconcile/test/test_openshift_saas_deploy.py +0 -188
- reconcile/test/test_openshift_saas_deploy_change_tester.py +0 -308
- reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +0 -65
- reconcile/test/test_openshift_serviceaccount_tokens.py +0 -282
- reconcile/test/test_openshift_tekton_resources.py +0 -265
- reconcile/test/test_openshift_upgrade_watcher.py +0 -223
- reconcile/test/test_prometheus_rules_tester.py +0 -151
- reconcile/test/test_quay_membership.py +0 -86
- reconcile/test/test_quay_mirror.py +0 -172
- reconcile/test/test_quay_mirror_org.py +0 -82
- reconcile/test/test_quay_repos.py +0 -59
- reconcile/test/test_queries.py +0 -53
- reconcile/test/test_repo_owners.py +0 -47
- reconcile/test/test_requests_sender.py +0 -139
- reconcile/test/test_saasherder.py +0 -1611
- reconcile/test/test_saasherder_allowed_secret_paths.py +0 -125
- reconcile/test/test_secret_reader.py +0 -153
- reconcile/test/test_slack_base.py +0 -183
- reconcile/test/test_slack_usergroups.py +0 -785
- reconcile/test/test_sql_query.py +0 -316
- reconcile/test/test_status_board.py +0 -258
- reconcile/test/test_terraform_aws_route53.py +0 -29
- reconcile/test/test_terraform_cloudflare_dns.py +0 -117
- reconcile/test/test_terraform_cloudflare_resources.py +0 -408
- reconcile/test/test_terraform_cloudflare_users.py +0 -747
- reconcile/test/test_terraform_repo.py +0 -440
- reconcile/test/test_terraform_resources.py +0 -519
- reconcile/test/test_terraform_tgw_attachments.py +0 -1295
- reconcile/test/test_terraform_users.py +0 -152
- reconcile/test/test_terraform_vpc_peerings.py +0 -576
- reconcile/test/test_terraform_vpc_peerings_build_desired_state.py +0 -1434
- reconcile/test/test_three_way_diff_strategy.py +0 -131
- reconcile/test/test_utils_jinja2.py +0 -130
- reconcile/test/test_vault_replication.py +0 -534
- reconcile/test/test_vault_utils.py +0 -47
- reconcile/test/test_version_bump.py +0 -18
- reconcile/test/test_vpc_peerings_validator.py +0 -194
- reconcile/test/test_wrong_region.py +0 -78
- release/__init__.py +0 -0
- release/test_version.py +0 -50
- release/version.py +0 -104
- tools/cli_commands/test/__init__.py +0 -0
- tools/cli_commands/test/conftest.py +0 -332
- tools/cli_commands/test/test_aws_cost_report.py +0 -258
- tools/cli_commands/test/test_cost_management_api.py +0 -326
- tools/cli_commands/test/test_gpg_encrypt.py +0 -235
- tools/cli_commands/test/test_openshift_cost_optimization_report.py +0 -255
- tools/cli_commands/test/test_openshift_cost_report.py +0 -295
- tools/cli_commands/test/test_util.py +0 -70
- tools/test/__init__.py +0 -0
- tools/test/conftest.py +0 -77
- tools/test/test_app_interface_metrics_exporter.py +0 -48
- tools/test/test_erv2.py +0 -80
- tools/test/test_get_container_images.py +0 -230
- tools/test/test_qontract_cli.py +0 -197
- tools/test/test_saas_promotion_state.py +0 -187
- tools/test/test_sd_app_sre_alert_report.py +0 -74
- tools/test/test_sre_checkpoints.py +0 -79
@@ -0,0 +1,293 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
import time
|
7
|
+
from collections.abc import Callable
|
8
|
+
from importlib import metadata
|
9
|
+
|
10
|
+
import click
|
11
|
+
from prometheus_client import (
|
12
|
+
push_to_gateway,
|
13
|
+
start_http_server,
|
14
|
+
)
|
15
|
+
from prometheus_client.exposition import basic_auth_handler
|
16
|
+
|
17
|
+
from reconcile.status import ExitCodes
|
18
|
+
from reconcile.utils.metrics import (
|
19
|
+
execution_counter,
|
20
|
+
pushgateway_registry,
|
21
|
+
pushgateway_run_status,
|
22
|
+
pushgateway_run_time,
|
23
|
+
run_status,
|
24
|
+
run_time,
|
25
|
+
)
|
26
|
+
from reconcile.utils.runtime.environment import (
|
27
|
+
LOG_DATEFMT,
|
28
|
+
log_fmt,
|
29
|
+
)
|
30
|
+
|
31
|
+
SHARDS = int(os.environ.get("SHARDS", 1))
|
32
|
+
SHARD_ID = int(os.environ.get("SHARD_ID", 0))
|
33
|
+
SHARD_ID_LABEL = os.environ.get("SHARD_KEY", f"{SHARD_ID}-{SHARDS}")
|
34
|
+
PREFIX_LOG_LEVEL = os.environ.get("PREFIX_LOG_LEVEL", "false")
|
35
|
+
|
36
|
+
INTEGRATION_NAME = os.environ.get("INTEGRATION_NAME")
|
37
|
+
COMMAND_NAME = os.environ.get("COMMAND_NAME", "qontract-reconcile")
|
38
|
+
|
39
|
+
RUN_ONCE = os.environ.get("RUN_ONCE")
|
40
|
+
DRY_RUN = (
|
41
|
+
os.environ.get("MANAGER_DRY_RUN")
|
42
|
+
if INTEGRATION_NAME == "integrations-manager"
|
43
|
+
else os.environ.get("DRY_RUN")
|
44
|
+
)
|
45
|
+
INTEGRATION_EXTRA_ARGS = os.environ.get("INTEGRATION_EXTRA_ARGS")
|
46
|
+
CONFIG = os.environ.get("CONFIG", "/config/config.toml")
|
47
|
+
PROMETHEUS_PORT = os.environ.get("PROMETHEUS_PORT", 9090)
|
48
|
+
|
49
|
+
LOG_FILE = os.environ.get("LOG_FILE")
|
50
|
+
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
|
51
|
+
SLEEP_DURATION_SECS = os.environ.get("SLEEP_DURATION_SECS", 600)
|
52
|
+
SLEEP_ON_ERROR = os.environ.get("SLEEP_ON_ERROR", 10)
|
53
|
+
|
54
|
+
PUSHGATEWAY_ENABLED = os.environ.get("PUSHGATEWAY_ENABLED", False)
|
55
|
+
|
56
|
+
LOG = logging.getLogger(__name__)
|
57
|
+
|
58
|
+
# Messages to stdout
|
59
|
+
STREAM_HANDLER = logging.StreamHandler(sys.stdout)
|
60
|
+
STREAM_HANDLER.setFormatter(
|
61
|
+
logging.Formatter(fmt=log_fmt(dry_run_option=DRY_RUN), datefmt=LOG_DATEFMT)
|
62
|
+
)
|
63
|
+
HANDLERS = [STREAM_HANDLER]
|
64
|
+
|
65
|
+
# Messages to the log file
|
66
|
+
if LOG_FILE is not None:
|
67
|
+
FILE_HANDLER = logging.FileHandler(LOG_FILE)
|
68
|
+
logFileFormat = "%(message)s"
|
69
|
+
if PREFIX_LOG_LEVEL == "true":
|
70
|
+
logFileFormat = "[%(levelname)s] %(message)s"
|
71
|
+
FILE_HANDLER.setFormatter(logging.Formatter(fmt=logFileFormat))
|
72
|
+
HANDLERS.append(FILE_HANDLER) # type: ignore
|
73
|
+
|
74
|
+
# Setting up the root logger
|
75
|
+
logging.basicConfig(level=LOG_LEVEL, handlers=HANDLERS)
|
76
|
+
|
77
|
+
|
78
|
+
class PushgatewayBadConfigError(Exception):
|
79
|
+
pass
|
80
|
+
|
81
|
+
|
82
|
+
def _parse_dry_run_flag(dry_run: str | None) -> str | None:
|
83
|
+
dry_run_options = ["--dry-run", "--no-dry-run"]
|
84
|
+
if dry_run is not None and dry_run not in dry_run_options:
|
85
|
+
msg = (
|
86
|
+
f'Invalid DRY_RUN option given: "{dry_run}".'
|
87
|
+
f"Only the following options are allowed: {dry_run_options}"
|
88
|
+
)
|
89
|
+
logging.error(msg)
|
90
|
+
raise ValueError(msg)
|
91
|
+
return dry_run if dry_run else None
|
92
|
+
|
93
|
+
|
94
|
+
def build_entry_point_args(
|
95
|
+
command: click.Command,
|
96
|
+
config: str,
|
97
|
+
dry_run: str | None,
|
98
|
+
integration_name: str,
|
99
|
+
extra_args: str | None,
|
100
|
+
) -> list[str]:
|
101
|
+
args = ["--config", config]
|
102
|
+
if dry_run_flag := _parse_dry_run_flag(dry_run):
|
103
|
+
args.append(dry_run_flag)
|
104
|
+
|
105
|
+
# if the integration_name is a known sub command,
|
106
|
+
# we add it right before the extra_args
|
107
|
+
if (
|
108
|
+
integration_name
|
109
|
+
and isinstance(command, click.MultiCommand)
|
110
|
+
and command.get_command(None, integration_name) # type: ignore
|
111
|
+
):
|
112
|
+
args.append(integration_name)
|
113
|
+
|
114
|
+
if extra_args is not None:
|
115
|
+
args.extend(extra_args.split())
|
116
|
+
return args
|
117
|
+
|
118
|
+
|
119
|
+
def build_entry_point_func(command_name: str) -> click.Command:
|
120
|
+
"""
|
121
|
+
Use the entry point information from setup.py to
|
122
|
+
find the function to invoke for a command.
|
123
|
+
"""
|
124
|
+
console_script_entry_points = {
|
125
|
+
ep.name: ep for ep in metadata.entry_points().select(group="console_scripts")
|
126
|
+
}
|
127
|
+
entry_point: metadata.EntryPoint | None = console_script_entry_points.get(
|
128
|
+
command_name
|
129
|
+
)
|
130
|
+
if entry_point:
|
131
|
+
return entry_point.load()
|
132
|
+
raise ValueError(
|
133
|
+
f"Command {command_name} unknown."
|
134
|
+
f"Have a look at setup.py for valid entry points."
|
135
|
+
)
|
136
|
+
|
137
|
+
|
138
|
+
def _get_pushgateway_env_vars() -> dict[str, str]:
|
139
|
+
env = {}
|
140
|
+
missing_vars = []
|
141
|
+
for var in ["PUSHGATEWAY_USERNAME", "PUSHGATEWAY_PASSWORD", "PUSHGATEWAY_URL"]:
|
142
|
+
value = os.environ.get(var)
|
143
|
+
if not value:
|
144
|
+
missing_vars.append(var)
|
145
|
+
continue
|
146
|
+
|
147
|
+
env[var] = value
|
148
|
+
|
149
|
+
if missing_vars:
|
150
|
+
missing_str = ", ".join(missing_vars)
|
151
|
+
raise PushgatewayBadConfigError(
|
152
|
+
f"Failed to check env variables to configure Pushgateway: {missing_str}"
|
153
|
+
)
|
154
|
+
|
155
|
+
return env
|
156
|
+
|
157
|
+
|
158
|
+
def _push_gateway_basic_auth_handler(
|
159
|
+
url: str,
|
160
|
+
method: str,
|
161
|
+
timeout: float | None,
|
162
|
+
headers: list[tuple[str, str]],
|
163
|
+
data: bytes,
|
164
|
+
) -> Callable[[], None]:
|
165
|
+
username = os.environ.get("PUSHGATEWAY_USERNAME")
|
166
|
+
password = os.environ.get("PUSHGATEWAY_PASSWORD")
|
167
|
+
|
168
|
+
# We should not get here, but this will make mypy happy
|
169
|
+
if not username or not password:
|
170
|
+
raise PushgatewayBadConfigError(
|
171
|
+
"Failed to check env variables to configure Pushgateway."
|
172
|
+
)
|
173
|
+
|
174
|
+
return basic_auth_handler(url, method, timeout, headers, data, username, password)
|
175
|
+
|
176
|
+
|
177
|
+
def main() -> None:
|
178
|
+
"""
|
179
|
+
This entry point script expects certain env variables
|
180
|
+
* COMMAND_NAME (optional, defaults to qontract-reconcile)
|
181
|
+
an entry point as defined in setup.py must be a click.Command
|
182
|
+
* INTEGRATION_NAME
|
183
|
+
used as name for the subcommand for command if present as a subcommand
|
184
|
+
on the click command
|
185
|
+
* INTEGRATION_EXTRA_ARGS (optional)
|
186
|
+
space separated list of arguments that will be passed to the command
|
187
|
+
or subcommand
|
188
|
+
* CONFIG
|
189
|
+
path to the config toml file
|
190
|
+
* LOG_LEVEL
|
191
|
+
Log level (defaults to INFO)
|
192
|
+
* LOG_FILE
|
193
|
+
path for the logfile to write to
|
194
|
+
* DRY_RUN (optional)
|
195
|
+
this is not a boolean but must contain the actual dry-run flag value,
|
196
|
+
so --dry-run or --no-dry-run
|
197
|
+
* RUN_ONCE (optional)
|
198
|
+
if 'true', execute the integration once and exit
|
199
|
+
otherwise run the integration in a loop controlled by SLEEP_DURATION_SECS
|
200
|
+
and SLEEP_ON_ERROR
|
201
|
+
* SLEEP_DURATION_SECS (default 600)
|
202
|
+
amount of seconds to sleep between successful integration runs
|
203
|
+
* SLEEP_ON_ERROR (default 10)
|
204
|
+
amount of seconds to sleep before another integration run is started
|
205
|
+
* PUSHGATEWAY_ENABLED (defaults to false)
|
206
|
+
send metrics to a Prometheus Pushgateway after the run. In expects
|
207
|
+
"PUSHGATEWAY_USERNAME", "PUSHGATEWAY_PASSWORD" and "PUSHGATEWAY_URL" to be defined.
|
208
|
+
|
209
|
+
|
210
|
+
Based on those variables, the following command will be executed
|
211
|
+
$COMMAND --config $CONFIG $DRY_RUN $INTEGRATION_NAME \
|
212
|
+
$INTEGRATION_EXTRA_ARGS
|
213
|
+
"""
|
214
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--help":
|
215
|
+
print(main.__doc__)
|
216
|
+
sys.exit(0)
|
217
|
+
|
218
|
+
if not INTEGRATION_NAME:
|
219
|
+
raise ValueError("INTEGRATION_NAME env variable is required")
|
220
|
+
|
221
|
+
start_http_server(int(PROMETHEUS_PORT))
|
222
|
+
|
223
|
+
command = build_entry_point_func(COMMAND_NAME)
|
224
|
+
while True:
|
225
|
+
args = build_entry_point_args(
|
226
|
+
command, CONFIG, DRY_RUN, INTEGRATION_NAME, INTEGRATION_EXTRA_ARGS
|
227
|
+
)
|
228
|
+
sleep = SLEEP_DURATION_SECS
|
229
|
+
start_time = time.monotonic()
|
230
|
+
# Running the integration via Click, so we don't have to replicate
|
231
|
+
# the CLI logic here
|
232
|
+
execution_counter.labels(
|
233
|
+
integration=INTEGRATION_NAME, shards=SHARDS, shard_id=SHARD_ID_LABEL
|
234
|
+
).inc()
|
235
|
+
try:
|
236
|
+
with command.make_context(info_name=COMMAND_NAME, args=args) as ctx: # type: ignore
|
237
|
+
ctx.ensure_object(dict)
|
238
|
+
command.invoke(ctx)
|
239
|
+
return_code = 0
|
240
|
+
# This is for when the integration explicitly
|
241
|
+
# calls sys.exit(N)
|
242
|
+
except SystemExit as exc_obj:
|
243
|
+
return_code = int(exc_obj.code) # type: ignore[arg-type]
|
244
|
+
# We have to be generic since we don't know what can happen
|
245
|
+
# in the integrations, but we want to continue the loop anyway
|
246
|
+
except Exception:
|
247
|
+
sleep = SLEEP_ON_ERROR
|
248
|
+
LOG.exception(f"Error running {COMMAND_NAME}")
|
249
|
+
return_code = ExitCodes.ERROR
|
250
|
+
|
251
|
+
time_spent = time.monotonic() - start_time
|
252
|
+
|
253
|
+
run_time.labels(
|
254
|
+
integration=INTEGRATION_NAME, shards=SHARDS, shard_id=SHARD_ID_LABEL
|
255
|
+
).set(time_spent)
|
256
|
+
run_status.labels(
|
257
|
+
integration=INTEGRATION_NAME, shards=SHARDS, shard_id=SHARD_ID_LABEL
|
258
|
+
).set(return_code)
|
259
|
+
|
260
|
+
if PUSHGATEWAY_ENABLED:
|
261
|
+
try:
|
262
|
+
env = _get_pushgateway_env_vars()
|
263
|
+
pushgateway_run_time.labels(
|
264
|
+
integration=INTEGRATION_NAME, shards=SHARDS, shard_id=SHARD_ID_LABEL
|
265
|
+
).set(time_spent)
|
266
|
+
pushgateway_run_status.labels(
|
267
|
+
integration=INTEGRATION_NAME, shards=SHARDS, shard_id=SHARD_ID_LABEL
|
268
|
+
).set(return_code)
|
269
|
+
|
270
|
+
grouping_key = {
|
271
|
+
"integration": INTEGRATION_NAME,
|
272
|
+
"shards": SHARDS,
|
273
|
+
"shard_id": SHARD_ID_LABEL,
|
274
|
+
}
|
275
|
+
push_to_gateway(
|
276
|
+
gateway=env["PUSHGATEWAY_URL"],
|
277
|
+
job="qontract-reconcile",
|
278
|
+
registry=pushgateway_registry,
|
279
|
+
handler=_push_gateway_basic_auth_handler,
|
280
|
+
grouping_key=grouping_key,
|
281
|
+
)
|
282
|
+
except PushgatewayBadConfigError as err:
|
283
|
+
LOG.exception(f"Error pushing to PushGateway: {err}")
|
284
|
+
return_code = ExitCodes.ERROR
|
285
|
+
|
286
|
+
if RUN_ONCE:
|
287
|
+
sys.exit(return_code)
|
288
|
+
|
289
|
+
time.sleep(int(sleep))
|
290
|
+
|
291
|
+
|
292
|
+
if __name__ == "__main__":
|
293
|
+
main()
|
reconcile/utils/binary.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import re
|
2
|
+
import shutil
|
2
3
|
import subprocess
|
3
|
-
from distutils.spawn import find_executable # pylint: disable=deprecated-module
|
4
4
|
from functools import wraps
|
5
5
|
|
6
6
|
|
@@ -13,7 +13,7 @@ def binary(binaries=None):
|
|
13
13
|
@wraps(f)
|
14
14
|
def f_binary(*args, **kwargs):
|
15
15
|
for b in binaries:
|
16
|
-
if not
|
16
|
+
if not shutil.which(b):
|
17
17
|
raise Exception(
|
18
18
|
f"Aborting: Could not find binary: {b}. "
|
19
19
|
+ f"Hint: https://command-not-found.com/{b}"
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# Merge Requests
|
2
|
+
|
3
|
+
Module responsible for abstracting a Gitlab Merge Request.
|
4
|
+
|
5
|
+
## Creating New MergeRequest Classes
|
6
|
+
|
7
|
+
Each type of merge request requires a class that inherits
|
8
|
+
from reconcile.utils.mr.base.MergeRequestBase.
|
9
|
+
|
10
|
+
That class has to comply with the specification below.
|
11
|
+
|
12
|
+
### Class Name
|
13
|
+
|
14
|
+
The class name is given by the class variable `name`. That name will be
|
15
|
+
used all around, the most important place being the SQS Message parameter
|
16
|
+
`pr_type`, used later to create an instance of the same class.
|
17
|
+
|
18
|
+
While the name can be anything it is mandatory that it is defined.
|
19
|
+
|
20
|
+
### Class Initialization
|
21
|
+
|
22
|
+
The `__init__` method has to call `super().__init__()` right after the minimum
|
23
|
+
parameters initialization. That is required to compose the SQS Message that
|
24
|
+
will later be used to create a new instance of the same class.
|
25
|
+
|
26
|
+
### Class Methods
|
27
|
+
|
28
|
+
The minimum methods that have to be defined are:
|
29
|
+
|
30
|
+
* `title`: a property that is used to give the Gitlab Merge Request a title.
|
31
|
+
It can also be used for building commit messages or as content to committed
|
32
|
+
files.
|
33
|
+
* `description`: a description for the Merge Request.
|
34
|
+
* `process`: this method is called when submitting the Merge Request to gitlab.
|
35
|
+
it's the place for the merge request changes, like creating, updating and
|
36
|
+
deleting files. The `process` method is called after the local branch is
|
37
|
+
created and right after it the `gitlab_cli.project.mergerequests.create()`
|
38
|
+
is called.
|
39
|
+
|
40
|
+
### Example
|
41
|
+
|
42
|
+
This is an example of a minimum implementation for a new MergeRequest class:
|
43
|
+
|
44
|
+
```python
|
45
|
+
from reconcile.utils.mr.base import MergeRequestBase
|
46
|
+
|
47
|
+
|
48
|
+
class CreateDeleteUser(MergeRequestBase):
|
49
|
+
|
50
|
+
name = 'create_delete_user_mr'
|
51
|
+
|
52
|
+
def __init__(self, username, paths):
|
53
|
+
self.username = username
|
54
|
+
self.paths = paths
|
55
|
+
|
56
|
+
# Called right after the minimum parameters to recreate
|
57
|
+
# an instance with the same data. This is important for
|
58
|
+
# building up the SQS message payload.
|
59
|
+
super().__init__()
|
60
|
+
|
61
|
+
@property
|
62
|
+
def title(self) -> str:
|
63
|
+
return f'[{self.name}] delete user {self.username}'
|
64
|
+
|
65
|
+
@property
|
66
|
+
def description(self) -> str:
|
67
|
+
return f'delete user {self.username}'
|
68
|
+
|
69
|
+
def process(self, gitlab_cli):
|
70
|
+
for path in self.paths:
|
71
|
+
gitlab_cli.delete_file(branch_name=self.branch,
|
72
|
+
file_path=path,
|
73
|
+
commit_message=self.title)
|
74
|
+
```
|
75
|
+
|
76
|
+
## Sending MRs to SQS
|
77
|
+
|
78
|
+
To send a Merge Request to SQS, create the corresponding MergeRequest object:
|
79
|
+
|
80
|
+
```python
|
81
|
+
from reconcile.utils.mr import CreateAppInterfaceNotificator
|
82
|
+
|
83
|
+
|
84
|
+
notification = {
|
85
|
+
'notification_type': 'Outage',
|
86
|
+
'description': 'The AppSRE team is currently investigating an outage',
|
87
|
+
'short_description': 'Short Description',
|
88
|
+
}
|
89
|
+
|
90
|
+
merge_request = CreateAppInterfaceNotificator(notification=notification)
|
91
|
+
|
92
|
+
```
|
93
|
+
|
94
|
+
then create the SQS Client instance:
|
95
|
+
|
96
|
+
```python
|
97
|
+
from reconcile import queries
|
98
|
+
|
99
|
+
from reconcile.utils.sqs_gateway import SQSGateway
|
100
|
+
from reconcile.utils.secret_reader import SecretReader
|
101
|
+
|
102
|
+
|
103
|
+
accounts = queries.get_queue_aws_accounts()
|
104
|
+
secretReader = SecretReader(queries.get_secret_reader_settings())
|
105
|
+
sqs_cli = SQSGateway(accounts, secret_reader=secret_reader)
|
106
|
+
```
|
107
|
+
|
108
|
+
and then submit the merge request to the SQS:
|
109
|
+
|
110
|
+
```python
|
111
|
+
merge_request.submit_to_sqs(sqs_cli=sqs_cli)
|
112
|
+
```
|
113
|
+
|
114
|
+
## Sending MRs to Gitlab
|
115
|
+
|
116
|
+
To get the message from SQS and use it for sending a Merge Request to gitlab,
|
117
|
+
first get the SQS messages:
|
118
|
+
|
119
|
+
|
120
|
+
```python
|
121
|
+
from reconcile import queries
|
122
|
+
|
123
|
+
from reconcile.utils.sqs_gateway import SQSGateway
|
124
|
+
from reconcile.utils.secret_reader import SecretReader
|
125
|
+
|
126
|
+
|
127
|
+
accounts = queries.get_queue_aws_accounts()
|
128
|
+
settings = queries.get_app_interface_settings()
|
129
|
+
|
130
|
+
secretReader = SecretReader(queries.get_secret_reader_settings())
|
131
|
+
sqs_cli = SQSGateway(accounts, secret_reader=secret_reader)
|
132
|
+
messages = sqs_cli.receive_messages()
|
133
|
+
```
|
134
|
+
|
135
|
+
then create the Gitlab client instance:
|
136
|
+
|
137
|
+
```python
|
138
|
+
from reconcile.utils.gitlab_api import GitLabApi
|
139
|
+
|
140
|
+
instance = queries.get_gitlab_instance()
|
141
|
+
saas_files = queries.get_saas_files_minimal()
|
142
|
+
gitlab_cli = GitLabApi(instance, project_id=gitlab_project_id,
|
143
|
+
settings=settings)
|
144
|
+
```
|
145
|
+
|
146
|
+
and then loop the messages, creating the MergeRequest objects and submitting
|
147
|
+
the merge requests:
|
148
|
+
|
149
|
+
```python
|
150
|
+
from reconcile.utils.mr import init_from_sqs_message
|
151
|
+
|
152
|
+
for message in messages:
|
153
|
+
receipt_handle, body = message[0], message[1]
|
154
|
+
merge_request = init_from_sqs_message(body)
|
155
|
+
merge_request.submit_to_gitlab(gitlab_cli=gitlab_cli)
|
156
|
+
sqs_cli.delete_message(receipt_handle)
|
157
|
+
```
|
158
|
+
|
159
|
+
## Using the MR Module for Qontract-reconcile Integrations
|
160
|
+
|
161
|
+
A given integration might be executed in different environments, like:
|
162
|
+
|
163
|
+
* Developer local machine.
|
164
|
+
* OpenShift cluster outside the VPN.
|
165
|
+
* OpenShift cluster inside the VPN.
|
166
|
+
* Jenkins periodic job.
|
167
|
+
|
168
|
+
Because we don't want the integrations to care if they are running inside or
|
169
|
+
outside the VPN, we use the `reconcile.mr_client_gateway.init()` to get the
|
170
|
+
on of SQS or GitLab client, according to the App Interface settings. Example:
|
171
|
+
|
172
|
+
```python
|
173
|
+
from reconcile import mr_client_gateway
|
174
|
+
from reconcile.utils.mr import CreateDeleteAwsAccessKey
|
175
|
+
|
176
|
+
|
177
|
+
def run(dry_run, gitlab_project_id):
|
178
|
+
...
|
179
|
+
mr = CreateDeleteAwsAccessKey(...)
|
180
|
+
with mr_client_gateway.init(gitlab_project_id=gitlab_project_id) as mr_cli:
|
181
|
+
mr.submit(cli=mr_cli)
|
182
|
+
```
|
183
|
+
|
184
|
+
If we want to override what's set in App Interface and get a specific client,
|
185
|
+
say `gitlab`, we would:
|
186
|
+
|
187
|
+
```python
|
188
|
+
from reconcile import mr_client_gateway
|
189
|
+
from reconcile.utils.mr import CreateDeleteAwsAccessKey
|
190
|
+
|
191
|
+
|
192
|
+
def run(dry_run, gitlab_project_id):
|
193
|
+
...
|
194
|
+
mr = CreateDeleteAwsAccessKey(...)
|
195
|
+
with mr_client_gateway.init(sqs_or_gitlab='gitlab',
|
196
|
+
gitlab_project_id=gitlab_project_id) as mr_cli:
|
197
|
+
mr.submit(cli=mr_cli)
|
198
|
+
```
|
reconcile/utils/oc_map.py
CHANGED
@@ -122,7 +122,7 @@ class OCMap:
|
|
122
122
|
connection_parameters=connection_parameters
|
123
123
|
)
|
124
124
|
try:
|
125
|
-
oc_client: OCCli | OCLogMsg = self._oc_cls(
|
125
|
+
oc_client: OCCli | OCLogMsg = self._oc_cls( # type: ignore[assignment]
|
126
126
|
connection_parameters=connection_parameters,
|
127
127
|
init_projects=self._init_projects,
|
128
128
|
init_api_resources=self._init_api_resources,
|
@@ -133,7 +133,7 @@ class OCMap:
|
|
133
133
|
cluster,
|
134
134
|
OCLogMsg(
|
135
135
|
log_level=logging.ERROR,
|
136
|
-
message=f"[{cluster}]
|
136
|
+
message=f"[{cluster}] is unreachable: {e}",
|
137
137
|
),
|
138
138
|
privileged,
|
139
139
|
)
|
tools/qontract_cli.py
CHANGED
File without changes
|
@@ -1,64 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: qontract-reconcile
|
3
|
-
Version: 0.10.1rc1201
|
4
|
-
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
|
-
Home-page: https://github.com/app-sre/qontract-reconcile
|
6
|
-
Author: Red Hat App-SRE Team
|
7
|
-
Author-email: sd-app-sre@redhat.com
|
8
|
-
License: Apache License 2.0
|
9
|
-
Classifier: Development Status :: 2 - Pre-Alpha
|
10
|
-
Classifier: Programming Language :: Python
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
13
|
-
Requires-Python: >=3.11
|
14
|
-
Requires-Dist: sretoolbox~=2.6
|
15
|
-
Requires-Dist: Click<9.0,>=7.0
|
16
|
-
Requires-Dist: gql==3.1.0
|
17
|
-
Requires-Dist: toml<0.11.0,>=0.10.0
|
18
|
-
Requires-Dist: jsonpath-rw<1.5.0,>=1.4.0
|
19
|
-
Requires-Dist: PyGithub<1.59,>=1.58
|
20
|
-
Requires-Dist: hvac<0.8.0,>=0.7.0
|
21
|
-
Requires-Dist: ldap3<2.10.0,>=2.9.1
|
22
|
-
Requires-Dist: anymarkup<0.9.0,>=0.7.0
|
23
|
-
Requires-Dist: python-gitlab~=4.6
|
24
|
-
Requires-Dist: semver~=3.0
|
25
|
-
Requires-Dist: boto3==1.34.94
|
26
|
-
Requires-Dist: botocore==1.34.94
|
27
|
-
Requires-Dist: urllib3~=2.2
|
28
|
-
Requires-Dist: slack-sdk<4.0,>=3.10
|
29
|
-
Requires-Dist: pypd<1.2.0,>=1.1.0
|
30
|
-
Requires-Dist: Jinja2<3.2.0,>=2.10.1
|
31
|
-
Requires-Dist: jira~=3.1
|
32
|
-
Requires-Dist: pyOpenSSL~=23.0
|
33
|
-
Requires-Dist: ruamel.yaml<0.18.0,>=0.17.22
|
34
|
-
Requires-Dist: terrascript==0.9.0
|
35
|
-
Requires-Dist: tabulate<0.9.0,>=0.8.6
|
36
|
-
Requires-Dist: UnleashClient~=5.11
|
37
|
-
Requires-Dist: prometheus-client~=0.8
|
38
|
-
Requires-Dist: sentry-sdk~=2.0
|
39
|
-
Requires-Dist: jenkins-job-builder~=4.3.0
|
40
|
-
Requires-Dist: parse==1.18.0
|
41
|
-
Requires-Dist: sendgrid<6.5.0,>=6.4.8
|
42
|
-
Requires-Dist: dnspython~=2.1
|
43
|
-
Requires-Dist: requests~=2.32
|
44
|
-
Requires-Dist: kubernetes~=24.0
|
45
|
-
Requires-Dist: websocket-client<0.55.0,>=0.35
|
46
|
-
Requires-Dist: sshtunnel>=0.4.0
|
47
|
-
Requires-Dist: croniter<1.1.0,>=1.0.15
|
48
|
-
Requires-Dist: pydantic~=1.10.6
|
49
|
-
Requires-Dist: MarkupSafe==2.1.1
|
50
|
-
Requires-Dist: filetype~=1.2.0
|
51
|
-
Requires-Dist: psycopg2~=2.9
|
52
|
-
Requires-Dist: packaging~=23.1
|
53
|
-
Requires-Dist: deepdiff==6.7.1
|
54
|
-
Requires-Dist: jsonpath-ng==1.5.3
|
55
|
-
Requires-Dist: networkx~=2.8
|
56
|
-
Requires-Dist: rich<14.0.0,>=13.3.0
|
57
|
-
Requires-Dist: dateparser~=1.1.7
|
58
|
-
Requires-Dist: pyjwt~=2.7
|
59
|
-
Requires-Dist: requests-oauthlib~=1.3
|
60
|
-
Requires-Dist: dt==1.1.61
|
61
|
-
Requires-Dist: jsonpatch~=1.33
|
62
|
-
Requires-Dist: jsonpointer~=2.4
|
63
|
-
Requires-Dist: yamllint==1.34.0
|
64
|
-
|
reconcile/test/__init__.py
DELETED
File without changes
|