qontract-reconcile 0.10.1rc745__py3-none-any.whl → 0.10.1rc747__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.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/RECORD +12 -11
- reconcile/cli.py +16 -2
- reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +16 -0
- reconcile/jira_permissions_validator.py +70 -16
- reconcile/terraform_repo.py +14 -11
- reconcile/test/test_terraform_repo.py +77 -62
- reconcile/utils/jira_client.py +14 -7
- {qontract_reconcile-0.10.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc747
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Home-page: https://github.com/app-sre/qontract-reconcile
|
6
6
|
Author: Red Hat App-SRE Team
|
{qontract_reconcile-0.10.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/RECORD
RENAMED
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
|
|
10
10
|
reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
|
11
11
|
reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
|
12
12
|
reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
|
13
|
-
reconcile/cli.py,sha256=
|
13
|
+
reconcile/cli.py,sha256=Vw8P6w0nPOGkMRvxpn4lR75jUeRoXLJ4R-WspWNX6Cg,98702
|
14
14
|
reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
|
15
15
|
reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
|
16
16
|
reconcile/dashdotdb_base.py,sha256=a5aPLVxyqPSbjdB0Ty-uliOtxwvEbbEljHJKxdK3-Zk,4813
|
@@ -47,7 +47,7 @@ reconcile/jenkins_roles.py,sha256=f8ELpZY36UjoaCpR_9LijQuIMuB6a7sVLFf_H1ct9Hc,44
|
|
47
47
|
reconcile/jenkins_webhooks.py,sha256=j8vhJMWcRhOdc9XzRSm0CPj84jsF3e4Syjm7r1BIsDE,1978
|
48
48
|
reconcile/jenkins_webhooks_cleaner.py,sha256=JsN_NVPfZJwv1JtSzZXDIHUqGiefL-DRffFnDGau9aY,1539
|
49
49
|
reconcile/jenkins_worker_fleets.py,sha256=PMNGOX0krubFjInPiFT0za0KCiWBLEcVDuXdKRd1BrE,5378
|
50
|
-
reconcile/jira_permissions_validator.py,sha256=
|
50
|
+
reconcile/jira_permissions_validator.py,sha256=iDsFdGqB8Zv9cjIVgYFq_N3xtRCrcR5uAZmcc51D-2o,13240
|
51
51
|
reconcile/jira_watcher.py,sha256=eyOQ92t8TFi6gogfNTO448h_h1CUyr24E0MPHc51R-o,3617
|
52
52
|
reconcile/ldap_users.py,sha256=uEWQ0V41tN9KCZi4ZKPamjrJ6djSpdpvDBo7yJ0e7ZI,3008
|
53
53
|
reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
|
@@ -112,7 +112,7 @@ reconcile/terraform_aws_route53.py,sha256=R8eZHlvP368nvtmLd_cMSK3RGxD7jso9qE7NP9
|
|
112
112
|
reconcile/terraform_cloudflare_dns.py,sha256=auU4bzeLwd4S8D8oqpqJbrCUoEdELXrgi7vHOedjYFk,13332
|
113
113
|
reconcile/terraform_cloudflare_resources.py,sha256=EbQQaoDnZ7brvRCpbFtwlD7KLk2hDVNcjhrJGaAywEk,15023
|
114
114
|
reconcile/terraform_cloudflare_users.py,sha256=1EbTHwJgiPkJpMP-Ag340QNgGK3mXn3dcC3DpLakudM,13987
|
115
|
-
reconcile/terraform_repo.py,sha256=
|
115
|
+
reconcile/terraform_repo.py,sha256=tQkMUEDEB6QfuWYwiW7H-NnX4SHxYK7mLZYZffKj-hE,16301
|
116
116
|
reconcile/terraform_resources.py,sha256=BN8XuJwjOt1ztruEAHydkd0YiBlb3fHZ7n0snZtRhck,19356
|
117
117
|
reconcile/terraform_tgw_attachments.py,sha256=k9Lf0ST65gmI6aUV6HnvxSGcKL7MGx_lN22OXuRGH9Y,16224
|
118
118
|
reconcile/terraform_users.py,sha256=9rgbM572LfmOSnV3uCP20G_Cw6T7due94g8rhhiz904,10225
|
@@ -300,6 +300,7 @@ reconcile/gql_definitions/membershipsources/roles.py,sha256=d3nv3GLsj_eKgwB1glsi
|
|
300
300
|
reconcile/gql_definitions/ocm_labels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
301
301
|
reconcile/gql_definitions/ocm_labels/clusters.py,sha256=MIVR5c8JxEOjsdGNpB737QyHGkjmHCycY6MAYNclPck,2916
|
302
302
|
reconcile/gql_definitions/ocm_labels/organizations.py,sha256=mmYB5C5Fp_nPzwBDKdKG4qWiLre2VkZ26U_2O-jRKC4,2001
|
303
|
+
reconcile/gql_definitions/ocm_oidc_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
303
304
|
reconcile/gql_definitions/ocm_subscription_labels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
304
305
|
reconcile/gql_definitions/openshift_cluster_bots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
305
306
|
reconcile/gql_definitions/openshift_cluster_bots/clusters.py,sha256=68meUg2Wgh91gplbPHAIlRWjnGg2RDrwtWd93QK6qRE,3672
|
@@ -345,7 +346,7 @@ reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.
|
|
345
346
|
reconcile/gql_definitions/terraform_init/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
346
347
|
reconcile/gql_definitions/terraform_init/aws_accounts.py,sha256=OJ0hDbRachRaDkL-OGT6-byr9cKdBiQDnNCpwUe3oJ8,2674
|
347
348
|
reconcile/gql_definitions/terraform_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
348
|
-
reconcile/gql_definitions/terraform_repo/terraform_repo.py,sha256=
|
349
|
+
reconcile/gql_definitions/terraform_repo/terraform_repo.py,sha256=_rdq3efy5Q3QFpI-vcs3-wacsXo_1fu1kVix_E83h5Q,3599
|
349
350
|
reconcile/gql_definitions/terraform_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
350
351
|
reconcile/gql_definitions/terraform_resources/database_access_manager.py,sha256=yv0_YC-LmhaKD_gyGG3le1w5BtypBjlsO894-Zgdg4U,4813
|
351
352
|
reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py,sha256=ElJTVH2cwNqJiLFl3Zi5zt9y7dN0yLmdtmwZC5X7ir0,42189
|
@@ -501,7 +502,7 @@ reconcile/test/test_terraform_aws_route53.py,sha256=xHggb8K1P76OyCfFcogbkmyKle-N
|
|
501
502
|
reconcile/test/test_terraform_cloudflare_dns.py,sha256=aQTXX8Vr4h9aWvJZTnpZEhMGYoBpT2d45ZxU_ECIQ6o,3425
|
502
503
|
reconcile/test/test_terraform_cloudflare_resources.py,sha256=NK_uktyWihkQ3gMN4bCaKerpi43CXAVYGIKTfcz05rY,13550
|
503
504
|
reconcile/test/test_terraform_cloudflare_users.py,sha256=RAFtMMdqZha3jNnNNsqbNQQUDSqUzdoM63rCw7fs4Fo,27456
|
504
|
-
reconcile/test/test_terraform_repo.py,sha256=
|
505
|
+
reconcile/test/test_terraform_repo.py,sha256=HTr8HMtLgecBTxKYbRAQFSty7tGzeKsv_fxtOAbDlNA,12184
|
505
506
|
reconcile/test/test_terraform_resources.py,sha256=EFCqPI5_G8hPRh1zmnU91o8wMeT2qK1CabDUa_X1rSk,15283
|
506
507
|
reconcile/test/test_terraform_tgw_attachments.py,sha256=cAq6exc-K-jtLla1CZUZQzVnBkyDnIlL7jybnddhLKc,36861
|
507
508
|
reconcile/test/test_terraform_users.py,sha256=XOAfGvITCJPI1LTlISmHbA4ONMQMkxYUMTsny7pQCFw,4319
|
@@ -617,7 +618,7 @@ reconcile/utils/helpers.py,sha256=k9svgFFZG7H5FvHYY0g5jJyvgvh2UDZxf0Ib221teag,11
|
|
617
618
|
reconcile/utils/imap_client.py,sha256=byFAJATbITJPsGECSbvXBOcCnoeTUpDFiEjzOAxLm_U,1975
|
618
619
|
reconcile/utils/instrumented_wrappers.py,sha256=eVwMoa6FCrYxLv3RML3WpZF9qKVfCTjMxphgVXG03OM,1073
|
619
620
|
reconcile/utils/jenkins_api.py,sha256=MyJSB_S3uYf3sXnt9t03-gZNQ7tbdd7Wusv3MoF2fRc,7113
|
620
|
-
reconcile/utils/jira_client.py,sha256=
|
621
|
+
reconcile/utils/jira_client.py,sha256=sCswWH-SctzPSElI9_oHv9LKuNnPfjP_FIOizxqh6nE,7693
|
621
622
|
reconcile/utils/jjb_client.py,sha256=Pdy0dLCFvD6GPCaC0tZydYgkVJPOxYXIiwWECZaFJBU,14551
|
622
623
|
reconcile/utils/jsonpath.py,sha256=NRpAEijKN4cMDjo7qivNPqpm0__GQQ1TiE0PBEBO45s,5572
|
623
624
|
reconcile/utils/jump_host.py,sha256=AdwmCZYNhRe53VwV2iAsUdVyUdVtSd4REmdThJDkM5w,4973
|
@@ -777,8 +778,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
777
778
|
tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
|
778
779
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
779
780
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
780
|
-
qontract_reconcile-0.10.
|
781
|
-
qontract_reconcile-0.10.
|
782
|
-
qontract_reconcile-0.10.
|
783
|
-
qontract_reconcile-0.10.
|
784
|
-
qontract_reconcile-0.10.
|
781
|
+
qontract_reconcile-0.10.1rc747.dist-info/METADATA,sha256=faEGscy4hQmj3OUFAXW7ImzUqwGYG--n1IM8fNXYPns,2382
|
782
|
+
qontract_reconcile-0.10.1rc747.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
783
|
+
qontract_reconcile-0.10.1rc747.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
|
784
|
+
qontract_reconcile-0.10.1rc747.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
785
|
+
qontract_reconcile-0.10.1rc747.dist-info/RECORD,,
|
reconcile/cli.py
CHANGED
@@ -1144,12 +1144,26 @@ def jenkins_webhooks_cleaner(ctx):
|
|
1144
1144
|
help="Throw and error in case of board permission errors. Useful for PR checks.",
|
1145
1145
|
default=True,
|
1146
1146
|
)
|
1147
|
+
@enable_extended_early_exit
|
1148
|
+
@extended_early_exit_cache_ttl_seconds
|
1149
|
+
@log_cached_log_output
|
1147
1150
|
@click.pass_context
|
1148
|
-
def jira_permissions_validator(
|
1151
|
+
def jira_permissions_validator(
|
1152
|
+
ctx,
|
1153
|
+
exit_on_permission_errors,
|
1154
|
+
enable_extended_early_exit,
|
1155
|
+
extended_early_exit_cache_ttl_seconds,
|
1156
|
+
log_cached_log_output,
|
1157
|
+
):
|
1149
1158
|
import reconcile.jira_permissions_validator
|
1150
1159
|
|
1151
1160
|
run_integration(
|
1152
|
-
reconcile.jira_permissions_validator,
|
1161
|
+
reconcile.jira_permissions_validator,
|
1162
|
+
ctx.obj,
|
1163
|
+
exit_on_permission_errors,
|
1164
|
+
enable_extended_early_exit=enable_extended_early_exit,
|
1165
|
+
extended_early_exit_cache_ttl_seconds=extended_early_exit_cache_ttl_seconds,
|
1166
|
+
log_cached_log_output=log_cached_log_output,
|
1153
1167
|
)
|
1154
1168
|
|
1155
1169
|
|
File without changes
|
@@ -52,6 +52,15 @@ query TerraformRepo {
|
|
52
52
|
projectPath
|
53
53
|
delete
|
54
54
|
requireFips
|
55
|
+
tfVersion
|
56
|
+
variables {
|
57
|
+
inputs {
|
58
|
+
...VaultSecret
|
59
|
+
}
|
60
|
+
outputs {
|
61
|
+
...VaultSecret
|
62
|
+
}
|
63
|
+
}
|
55
64
|
}
|
56
65
|
}
|
57
66
|
"""
|
@@ -82,6 +91,11 @@ class AWSAccountV1(ConfiguredBaseModel):
|
|
82
91
|
terraform_state: Optional[TerraformStateAWSV1] = Field(..., alias="terraformState")
|
83
92
|
|
84
93
|
|
94
|
+
class TerraformRepoVariablesV1(ConfiguredBaseModel):
|
95
|
+
inputs: VaultSecret = Field(..., alias="inputs")
|
96
|
+
outputs: VaultSecret = Field(..., alias="outputs")
|
97
|
+
|
98
|
+
|
85
99
|
class TerraformRepoV1(ConfiguredBaseModel):
|
86
100
|
account: AWSAccountV1 = Field(..., alias="account")
|
87
101
|
name: str = Field(..., alias="name")
|
@@ -90,6 +104,8 @@ class TerraformRepoV1(ConfiguredBaseModel):
|
|
90
104
|
project_path: str = Field(..., alias="projectPath")
|
91
105
|
delete: Optional[bool] = Field(..., alias="delete")
|
92
106
|
require_fips: Optional[bool] = Field(..., alias="requireFips")
|
107
|
+
tf_version: str = Field(..., alias="tfVersion")
|
108
|
+
variables: Optional[TerraformRepoVariablesV1] = Field(..., alias="variables")
|
93
109
|
|
94
110
|
|
95
111
|
class TerraformRepoQueryData(ConfiguredBaseModel):
|
@@ -2,14 +2,11 @@ import logging
|
|
2
2
|
import sys
|
3
3
|
from collections.abc import Callable, Iterable
|
4
4
|
from enum import IntFlag, auto
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any, TypedDict
|
6
6
|
|
7
7
|
from jira import JIRAError
|
8
8
|
from pydantic import BaseModel
|
9
9
|
|
10
|
-
from reconcile.gql_definitions.jira_permissions_validator.jira_boards_for_permissions_validator import (
|
11
|
-
DEFINITION as JIRA_BOARDS_DEFINITION,
|
12
|
-
)
|
13
10
|
from reconcile.gql_definitions.jira_permissions_validator.jira_boards_for_permissions_validator import (
|
14
11
|
JiraBoardV1,
|
15
12
|
)
|
@@ -24,10 +21,17 @@ from reconcile.typed_queries.jira_settings import get_jira_settings
|
|
24
21
|
from reconcile.typed_queries.jiralert_settings import get_jiralert_settings
|
25
22
|
from reconcile.utils import gql, metrics
|
26
23
|
from reconcile.utils.disabled_integrations import integration_is_enabled
|
24
|
+
from reconcile.utils.extended_early_exit import (
|
25
|
+
ExtendedEarlyExitRunnerResult,
|
26
|
+
extended_early_exit_run,
|
27
|
+
)
|
27
28
|
from reconcile.utils.jira_client import JiraClient, JiraWatcherSettings
|
28
29
|
from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
|
30
|
+
from reconcile.utils.semver_helper import make_semver
|
31
|
+
from reconcile.utils.unleash import get_feature_toggle_state
|
29
32
|
|
30
33
|
QONTRACT_INTEGRATION = "jira-permissions-validator"
|
34
|
+
QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
|
31
35
|
|
32
36
|
NameToIdMap = dict[str, str]
|
33
37
|
|
@@ -59,6 +63,15 @@ class ValidationError(IntFlag):
|
|
59
63
|
INVALID_COMPONENT = auto()
|
60
64
|
|
61
65
|
|
66
|
+
class RunnerParams(TypedDict):
|
67
|
+
exit_on_permission_errors: bool
|
68
|
+
boards: list[JiraBoardV1]
|
69
|
+
|
70
|
+
|
71
|
+
class CacheSource(TypedDict):
|
72
|
+
boards: list
|
73
|
+
|
74
|
+
|
62
75
|
def board_is_valid(
|
63
76
|
jira: JiraClient,
|
64
77
|
board: JiraBoardV1,
|
@@ -90,7 +103,7 @@ def board_is_valid(
|
|
90
103
|
error |= ValidationError.INVALID_COMPONENT
|
91
104
|
|
92
105
|
issue_type = board.issue_type if board.issue_type else default_issue_type
|
93
|
-
project_issue_types = jira.project_issue_types(
|
106
|
+
project_issue_types = jira.project_issue_types()
|
94
107
|
project_issue_types_str = [i.name for i in project_issue_types]
|
95
108
|
if issue_type not in project_issue_types_str:
|
96
109
|
logging.error(
|
@@ -192,7 +205,6 @@ def validate_boards(
|
|
192
205
|
) -> bool:
|
193
206
|
error = False
|
194
207
|
jira_clients: dict[str, JiraClient] = {}
|
195
|
-
public_projects_cache: dict[str, list[str]] = {}
|
196
208
|
for board in jira_boards:
|
197
209
|
logging.debug(f"[{board.name}] checking ...")
|
198
210
|
if board.server.server_url not in jira_clients:
|
@@ -204,10 +216,6 @@ def validate_boards(
|
|
204
216
|
)
|
205
217
|
jira = jira_clients[board.server.server_url]
|
206
218
|
jira.project = board.name
|
207
|
-
if board.server.server_url not in public_projects_cache:
|
208
|
-
public_projects_cache[board.server.server_url] = jira.public_projects()
|
209
|
-
public_projects = public_projects_cache[board.server.server_url]
|
210
|
-
|
211
219
|
try:
|
212
220
|
error_flags = board_is_valid(
|
213
221
|
jira=jira,
|
@@ -215,7 +223,7 @@ def validate_boards(
|
|
215
223
|
default_issue_type=default_issue_type,
|
216
224
|
default_reopen_state=default_reopen_state,
|
217
225
|
jira_server_priorities={p.name: p.id for p in jira.priorities()},
|
218
|
-
public_projects=public_projects,
|
226
|
+
public_projects=jira.public_projects(),
|
219
227
|
)
|
220
228
|
match error_flags:
|
221
229
|
case 0:
|
@@ -256,13 +264,59 @@ def get_jira_boards(query_func: Callable) -> list[JiraBoardV1]:
|
|
256
264
|
]
|
257
265
|
|
258
266
|
|
259
|
-
def
|
267
|
+
def export_boards(boards: list[JiraBoardV1]) -> list[dict]:
|
268
|
+
return [board.dict() for board in boards]
|
269
|
+
|
270
|
+
|
271
|
+
def run(
|
272
|
+
dry_run: bool,
|
273
|
+
exit_on_permission_errors: bool,
|
274
|
+
enable_extended_early_exit: bool = False,
|
275
|
+
extended_early_exit_cache_ttl_seconds: int = 3600,
|
276
|
+
log_cached_log_output: bool = False,
|
277
|
+
) -> None:
|
278
|
+
gql_api = gql.get_api()
|
279
|
+
boards = get_jira_boards(query_func=gql_api.query)
|
280
|
+
runner_params: RunnerParams = dict(
|
281
|
+
exit_on_permission_errors=exit_on_permission_errors,
|
282
|
+
boards=boards,
|
283
|
+
)
|
284
|
+
if enable_extended_early_exit and get_feature_toggle_state(
|
285
|
+
"jira-permissions-validator-extended-early-exit",
|
286
|
+
default=True,
|
287
|
+
):
|
288
|
+
vault_settings = get_app_interface_vault_settings()
|
289
|
+
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
290
|
+
|
291
|
+
cache_source = CacheSource(
|
292
|
+
boards=export_boards(boards),
|
293
|
+
)
|
294
|
+
extended_early_exit_run(
|
295
|
+
integration=QONTRACT_INTEGRATION,
|
296
|
+
integration_version=QONTRACT_INTEGRATION_VERSION,
|
297
|
+
# don't use `dry_run` in the cache key because this is a read-only integration
|
298
|
+
dry_run=False,
|
299
|
+
cache_source=cache_source,
|
300
|
+
shard="",
|
301
|
+
ttl_seconds=extended_early_exit_cache_ttl_seconds,
|
302
|
+
logger=logging.getLogger(),
|
303
|
+
runner=runner,
|
304
|
+
runner_params=runner_params,
|
305
|
+
secret_reader=secret_reader,
|
306
|
+
log_cached_log_output=log_cached_log_output,
|
307
|
+
)
|
308
|
+
else:
|
309
|
+
runner(**runner_params)
|
310
|
+
|
311
|
+
|
312
|
+
def runner(
|
313
|
+
exit_on_permission_errors: bool, boards: list[JiraBoardV1]
|
314
|
+
) -> ExtendedEarlyExitRunnerResult:
|
260
315
|
gql_api = gql.get_api()
|
261
316
|
settings = get_jira_settings(gql_api=gql_api)
|
262
317
|
jiralert_settings = get_jiralert_settings(query_func=gql_api.query)
|
263
318
|
vault_settings = get_app_interface_vault_settings()
|
264
319
|
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
265
|
-
boards = get_jira_boards(query_func=gql_api.query)
|
266
320
|
|
267
321
|
with metrics.transactional_metrics("jira-boards") as metrics_container:
|
268
322
|
error = validate_boards(
|
@@ -278,8 +332,8 @@ def run(dry_run: bool, exit_on_permission_errors: bool) -> None:
|
|
278
332
|
if error:
|
279
333
|
sys.exit(ExitCodes.ERROR)
|
280
334
|
|
335
|
+
return ExtendedEarlyExitRunnerResult(payload=export_boards(boards), applied_count=0)
|
336
|
+
|
281
337
|
|
282
338
|
def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
283
|
-
return {
|
284
|
-
"boards": gql.get_api().query(JIRA_BOARDS_DEFINITION)["jira_boards"],
|
285
|
-
}
|
339
|
+
return {"boards": export_boards(get_jira_boards(query_func=gql.get_api().query))}
|
reconcile/terraform_repo.py
CHANGED
@@ -12,8 +12,10 @@ from pydantic import (
|
|
12
12
|
)
|
13
13
|
|
14
14
|
from reconcile import queries
|
15
|
+
from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
|
15
16
|
from reconcile.gql_definitions.terraform_repo.terraform_repo import (
|
16
17
|
TerraformRepoV1,
|
18
|
+
TerraformRepoVariablesV1,
|
17
19
|
query,
|
18
20
|
)
|
19
21
|
from reconcile.utils import gql
|
@@ -35,22 +37,19 @@ from reconcile.utils.state import (
|
|
35
37
|
)
|
36
38
|
|
37
39
|
|
38
|
-
class RepoSecret(BaseModel):
|
39
|
-
path: str
|
40
|
-
version: Optional[int]
|
41
|
-
|
42
|
-
|
43
40
|
class RepoOutput(BaseModel):
|
44
41
|
repository: str
|
45
42
|
name: str
|
46
43
|
ref: str
|
47
44
|
project_path: str
|
48
45
|
delete: bool
|
49
|
-
|
46
|
+
aws_creds: VaultSecret
|
47
|
+
variables: Optional[TerraformRepoVariablesV1]
|
50
48
|
bucket: Optional[str]
|
51
49
|
region: Optional[str]
|
52
50
|
bucket_path: Optional[str]
|
53
51
|
require_fips: bool
|
52
|
+
tf_version: str
|
54
53
|
|
55
54
|
|
56
55
|
class OutputFile(BaseModel):
|
@@ -126,13 +125,16 @@ class TerraformRepoIntegration(
|
|
126
125
|
recreate_state=True,
|
127
126
|
)
|
128
127
|
|
129
|
-
def print_output(self, diff: list[TerraformRepoV1], dry_run: bool) ->
|
128
|
+
def print_output(self, diff: list[TerraformRepoV1], dry_run: bool) -> OutputFile:
|
130
129
|
"""Parses and prints the output of a Terraform Repo diff for the executor
|
131
130
|
|
132
131
|
:param diff: list of terraform repos to be acted on
|
133
132
|
:type diff: list[TerraformRepoV1]
|
134
133
|
:param dry_run: whether the executor should perform a tf apply
|
135
134
|
:type dry_run: bool
|
135
|
+
|
136
|
+
:return: output of diff (used for testing)
|
137
|
+
:rtype: OutputFile
|
136
138
|
"""
|
137
139
|
actions_list: list[RepoOutput] = []
|
138
140
|
|
@@ -144,10 +146,9 @@ class TerraformRepoIntegration(
|
|
144
146
|
project_path=repo.project_path,
|
145
147
|
delete=repo.delete or False,
|
146
148
|
require_fips=repo.require_fips or False,
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
),
|
149
|
+
tf_version=repo.tf_version,
|
150
|
+
aws_creds=repo.account.automation_token,
|
151
|
+
variables=repo.variables,
|
151
152
|
)
|
152
153
|
# terraform-repo will store its statefiles in a specified directory if there is a
|
153
154
|
# terraform-state yaml file associated with the AWS account and a configuration is
|
@@ -179,6 +180,8 @@ class TerraformRepoIntegration(
|
|
179
180
|
else:
|
180
181
|
print(yaml.safe_dump(data=output.dict(), explicit_start=True))
|
181
182
|
|
183
|
+
return output
|
184
|
+
|
182
185
|
def get_repos(self, query_func: Callable) -> list[TerraformRepoV1]:
|
183
186
|
"""Gets a list of terraform repos defined in App Interface
|
184
187
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from unittest.mock import MagicMock
|
2
2
|
|
3
3
|
import pytest
|
4
|
-
import yaml
|
5
4
|
|
6
5
|
from reconcile.gql_definitions.fragments.terraform_state import (
|
7
6
|
AWSTerraformStateIntegrationsV1,
|
@@ -10,9 +9,12 @@ from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
|
|
10
9
|
from reconcile.gql_definitions.terraform_repo.terraform_repo import (
|
11
10
|
AWSAccountV1,
|
12
11
|
TerraformRepoV1,
|
12
|
+
TerraformRepoVariablesV1,
|
13
13
|
TerraformStateAWSV1,
|
14
14
|
)
|
15
15
|
from reconcile.terraform_repo import (
|
16
|
+
OutputFile,
|
17
|
+
RepoOutput,
|
16
18
|
TerraformRepoIntegration,
|
17
19
|
TerraformRepoIntegrationParams,
|
18
20
|
)
|
@@ -21,8 +23,10 @@ from reconcile.utils.state import State
|
|
21
23
|
|
22
24
|
A_REPO = "https://git-example/tf-repo-example"
|
23
25
|
A_REPO_SHA = "a390f5cb20322c90861d6d80e9b70c6a579be1d0"
|
26
|
+
A_REPO_VERSION = "1.4.5"
|
24
27
|
B_REPO = "https://git-example/tf-repo-example2"
|
25
28
|
B_REPO_SHA = "94edb90815e502b387c25358f5ec602e52d0bfbb"
|
29
|
+
B_REPO_VERSION = "1.5.7"
|
26
30
|
AWS_UID = "000000000000"
|
27
31
|
AUTOMATION_TOKEN_PATH = "aws-secrets/terraform/foo"
|
28
32
|
STATE_REGION = "us-east-1"
|
@@ -31,7 +35,7 @@ STATE_PROVIDER = "s3"
|
|
31
35
|
|
32
36
|
|
33
37
|
@pytest.fixture
|
34
|
-
def existing_repo(aws_account) -> TerraformRepoV1:
|
38
|
+
def existing_repo(aws_account, tf_variables) -> TerraformRepoV1:
|
35
39
|
return TerraformRepoV1(
|
36
40
|
name="a_repo",
|
37
41
|
repository=A_REPO,
|
@@ -40,27 +44,34 @@ def existing_repo(aws_account) -> TerraformRepoV1:
|
|
40
44
|
projectPath="tf",
|
41
45
|
delete=False,
|
42
46
|
requireFips=True,
|
47
|
+
tfVersion=A_REPO_VERSION,
|
48
|
+
variables=tf_variables,
|
43
49
|
)
|
44
50
|
|
45
51
|
|
46
52
|
@pytest.fixture
|
47
|
-
def existing_repo_output() ->
|
48
|
-
return
|
49
|
-
dry_run
|
50
|
-
repos
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
53
|
+
def existing_repo_output(tf_variables) -> OutputFile:
|
54
|
+
return OutputFile(
|
55
|
+
dry_run=True,
|
56
|
+
repos=[
|
57
|
+
RepoOutput(
|
58
|
+
repository=A_REPO,
|
59
|
+
name="a_repo",
|
60
|
+
ref=A_REPO_SHA,
|
61
|
+
project_path="tf",
|
62
|
+
delete=False,
|
63
|
+
aws_creds=VaultSecret(
|
64
|
+
path=AUTOMATION_TOKEN_PATH, version=1, field="all", format=None
|
65
|
+
),
|
66
|
+
bucket=STATE_BUCKET,
|
67
|
+
region=STATE_REGION,
|
68
|
+
bucket_path="tf-repo",
|
69
|
+
require_fips=True,
|
70
|
+
tf_version=A_REPO_VERSION,
|
71
|
+
variables=tf_variables,
|
72
|
+
)
|
73
|
+
],
|
74
|
+
)
|
64
75
|
|
65
76
|
|
66
77
|
@pytest.fixture
|
@@ -73,27 +84,34 @@ def new_repo(aws_account_no_state) -> TerraformRepoV1:
|
|
73
84
|
projectPath="tf",
|
74
85
|
delete=False,
|
75
86
|
requireFips=False,
|
87
|
+
tfVersion=B_REPO_VERSION,
|
88
|
+
variables=None,
|
76
89
|
)
|
77
90
|
|
78
91
|
|
79
92
|
@pytest.fixture
|
80
|
-
def new_repo_output() ->
|
81
|
-
return
|
82
|
-
dry_run
|
83
|
-
repos
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
93
|
+
def new_repo_output() -> OutputFile:
|
94
|
+
return OutputFile(
|
95
|
+
dry_run=True,
|
96
|
+
repos=[
|
97
|
+
RepoOutput(
|
98
|
+
repository=B_REPO,
|
99
|
+
name="b_repo",
|
100
|
+
ref=B_REPO_SHA,
|
101
|
+
project_path="tf",
|
102
|
+
delete=False,
|
103
|
+
aws_creds=VaultSecret(
|
104
|
+
path=AUTOMATION_TOKEN_PATH, version=1, field="all", format=None
|
105
|
+
),
|
106
|
+
bucket=None,
|
107
|
+
region=None,
|
108
|
+
bucket_path=None,
|
109
|
+
require_fips=False,
|
110
|
+
tf_version=B_REPO_VERSION,
|
111
|
+
variables=None,
|
112
|
+
)
|
113
|
+
],
|
114
|
+
)
|
97
115
|
|
98
116
|
|
99
117
|
@pytest.fixture()
|
@@ -101,6 +119,18 @@ def automation_token() -> VaultSecret:
|
|
101
119
|
return VaultSecret(path=AUTOMATION_TOKEN_PATH, version=1, field="all", format=None)
|
102
120
|
|
103
121
|
|
122
|
+
@pytest.fixture()
|
123
|
+
def tf_variables() -> TerraformRepoVariablesV1:
|
124
|
+
return TerraformRepoVariablesV1(
|
125
|
+
inputs=VaultSecret(
|
126
|
+
path="terraform-repo/inputs/abc", field="all", version=2, format=None
|
127
|
+
),
|
128
|
+
outputs=VaultSecret(
|
129
|
+
path="terraform-repo/outputs/abc", field="all", version=2, format=None
|
130
|
+
),
|
131
|
+
)
|
132
|
+
|
133
|
+
|
104
134
|
@pytest.fixture()
|
105
135
|
def terraform_state(terraform_state_integrations) -> TerraformStateAWSV1:
|
106
136
|
return TerraformStateAWSV1(
|
@@ -143,13 +173,6 @@ def int_params() -> TerraformRepoIntegrationParams:
|
|
143
173
|
return TerraformRepoIntegrationParams(output_file=None, validate_git=False)
|
144
174
|
|
145
175
|
|
146
|
-
@pytest.fixture
|
147
|
-
def int_params_print_to_tmp(tmp_path) -> TerraformRepoIntegrationParams:
|
148
|
-
return TerraformRepoIntegrationParams(
|
149
|
-
output_file=f"{tmp_path}/tf-repo.yaml", validate_git=False
|
150
|
-
)
|
151
|
-
|
152
|
-
|
153
176
|
@pytest.fixture()
|
154
177
|
def state_mock() -> MagicMock:
|
155
178
|
return MagicMock(spec=State)
|
@@ -253,7 +276,7 @@ def test_delete_repo_without_flag(existing_repo, int_params):
|
|
253
276
|
)
|
254
277
|
|
255
278
|
|
256
|
-
def test_get_repo_state(s3_state_builder, int_params, existing_repo):
|
279
|
+
def test_get_repo_state(s3_state_builder, int_params, existing_repo, tf_variables):
|
257
280
|
state = s3_state_builder({
|
258
281
|
"ls": [
|
259
282
|
"/a_repo",
|
@@ -266,6 +289,8 @@ def test_get_repo_state(s3_state_builder, int_params, existing_repo):
|
|
266
289
|
"projectPath": "tf",
|
267
290
|
"delete": False,
|
268
291
|
"requireFips": True,
|
292
|
+
"tfVersion": A_REPO_VERSION,
|
293
|
+
"variables": tf_variables,
|
269
294
|
"account": {
|
270
295
|
"name": "foo",
|
271
296
|
"uid": AWS_UID,
|
@@ -319,15 +344,13 @@ def test_update_repo_state(int_params, existing_repo, state_mock):
|
|
319
344
|
# these two output tests are to ensure that there isn't a sudden change to outputs that throws
|
320
345
|
# off tf-executor
|
321
346
|
def test_output_correct_statefile(
|
322
|
-
|
347
|
+
int_params, existing_repo, existing_repo_output, state_mock
|
323
348
|
):
|
324
|
-
integration = TerraformRepoIntegration(params=
|
349
|
+
integration = TerraformRepoIntegration(params=int_params)
|
325
350
|
|
326
351
|
existing_state: list = []
|
327
352
|
desired_state = [existing_repo]
|
328
353
|
|
329
|
-
expected_output = yaml.safe_load(existing_repo_output)
|
330
|
-
|
331
354
|
diff = integration.calculate_diff(
|
332
355
|
existing_state=existing_state,
|
333
356
|
desired_state=desired_state,
|
@@ -337,24 +360,19 @@ def test_output_correct_statefile(
|
|
337
360
|
)
|
338
361
|
|
339
362
|
assert diff
|
340
|
-
integration.print_output(diff, True)
|
341
|
-
|
342
|
-
with open(f"{tmp_path}/tf-repo.yaml", "r", encoding="locale") as output:
|
343
|
-
yaml_rep = yaml.safe_load(output)
|
363
|
+
current_output = integration.print_output(diff, True)
|
344
364
|
|
345
|
-
|
365
|
+
assert existing_repo_output == current_output
|
346
366
|
|
347
367
|
|
348
368
|
def test_output_correct_no_statefile(
|
349
|
-
|
369
|
+
int_params, new_repo, new_repo_output, tmp_path, state_mock
|
350
370
|
):
|
351
|
-
integration = TerraformRepoIntegration(params=
|
371
|
+
integration = TerraformRepoIntegration(params=int_params)
|
352
372
|
|
353
373
|
existing_state: list = []
|
354
374
|
desired_state = [new_repo]
|
355
375
|
|
356
|
-
expected_output = yaml.safe_load(new_repo_output)
|
357
|
-
|
358
376
|
diff = integration.calculate_diff(
|
359
377
|
existing_state=existing_state,
|
360
378
|
desired_state=desired_state,
|
@@ -364,12 +382,9 @@ def test_output_correct_no_statefile(
|
|
364
382
|
)
|
365
383
|
|
366
384
|
assert diff
|
367
|
-
integration.print_output(diff, True)
|
368
|
-
|
369
|
-
with open(f"{tmp_path}/tf-repo.yaml", "r", encoding="locale") as output:
|
370
|
-
yaml_rep = yaml.safe_load(output)
|
385
|
+
current_output = integration.print_output(diff, True)
|
371
386
|
|
372
|
-
|
387
|
+
assert new_repo_output == current_output
|
373
388
|
|
374
389
|
|
375
390
|
def test_fail_on_multiple_repos_dry_run(int_params, existing_repo, new_repo):
|
reconcile/utils/jira_client.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import functools
|
3
4
|
import logging
|
4
5
|
from collections.abc import (
|
5
6
|
Iterable,
|
@@ -82,6 +83,11 @@ class JiraClient:
|
|
82
83
|
self.project = project
|
83
84
|
self.jira = jira_api
|
84
85
|
|
86
|
+
# some caches
|
87
|
+
self.priorities = functools.lru_cache(maxsize=None)(self._priorities)
|
88
|
+
self.public_projects = functools.lru_cache(maxsize=None)(self._public_projects)
|
89
|
+
self.my_permissions = functools.lru_cache(maxsize=None)(self._my_permissions)
|
90
|
+
|
85
91
|
def _deprecated_init(
|
86
92
|
self, jira_board: Mapping[str, Any], settings: Optional[Mapping]
|
87
93
|
) -> None:
|
@@ -174,11 +180,12 @@ class JiraClient:
|
|
174
180
|
)
|
175
181
|
return issue
|
176
182
|
|
183
|
+
def _my_permissions(self, project: str) -> dict[str, Any]:
|
184
|
+
return self.jira.my_permissions(projectKey=project)["permissions"]
|
185
|
+
|
177
186
|
def can_i(self, permission: str) -> bool:
|
178
187
|
return bool(
|
179
|
-
self.
|
180
|
-
permission
|
181
|
-
]["havePermission"]
|
188
|
+
self.my_permissions(project=self.project)[permission]["havePermission"]
|
182
189
|
)
|
183
190
|
|
184
191
|
def can_create_issues(self) -> bool:
|
@@ -187,10 +194,10 @@ class JiraClient:
|
|
187
194
|
def can_transition_issues(self) -> bool:
|
188
195
|
return self.can_i("TRANSITION_ISSUES")
|
189
196
|
|
190
|
-
def project_issue_types(self
|
197
|
+
def project_issue_types(self) -> list[IssueType]:
|
191
198
|
return [
|
192
199
|
IssueType(id=t.id, name=t.name, statuses=[s.name for s in t.statuses])
|
193
|
-
for t in self.jira.issue_types_for_project(project)
|
200
|
+
for t in self.jira.issue_types_for_project(self.project)
|
194
201
|
]
|
195
202
|
|
196
203
|
def security_levels(self) -> list[SecurityLevel]:
|
@@ -201,7 +208,7 @@ class JiraClient:
|
|
201
208
|
scheme = self.jira.project_issue_security_level_scheme(self.project)
|
202
209
|
return [SecurityLevel(id=level.id, name=level.name) for level in scheme.levels]
|
203
210
|
|
204
|
-
def
|
211
|
+
def _priorities(self) -> list[Priority]:
|
205
212
|
"""Return a list of all available Jira priorities."""
|
206
213
|
return [Priority(id=p.id, name=p.name) for p in self.jira.priorities()]
|
207
214
|
|
@@ -210,7 +217,7 @@ class JiraClient:
|
|
210
217
|
scheme = self.jira.project_priority_scheme(self.project)
|
211
218
|
return scheme.optionIds
|
212
219
|
|
213
|
-
def
|
220
|
+
def _public_projects(self) -> list[str]:
|
214
221
|
"""Return a list of all public available projects."""
|
215
222
|
if not self.server:
|
216
223
|
raise RuntimeError("JiraClient.server is not set.")
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc745.dist-info → qontract_reconcile-0.10.1rc747.dist-info}/top_level.txt
RENAMED
File without changes
|