qontract-reconcile 0.10.2.dev215__py3-none-any.whl → 0.10.2.dev217__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.dev215.dist-info → qontract_reconcile-0.10.2.dev217.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev215.dist-info → qontract_reconcile-0.10.2.dev217.dist-info}/RECORD +20 -20
- reconcile/automated_actions/config/integration.py +15 -5
- reconcile/cli.py +620 -505
- reconcile/gitlab_housekeeping.py +57 -58
- reconcile/gitlab_permissions.py +13 -5
- reconcile/gitlab_projects.py +5 -3
- reconcile/gql_definitions/automated_actions/instance.py +41 -2
- reconcile/gql_definitions/introspection.json +177 -1
- reconcile/integrations_manager.py +32 -41
- reconcile/jenkins_job_builder.py +1 -1
- reconcile/jenkins_job_builds_cleaner.py +10 -5
- reconcile/jenkins_job_cleaner.py +6 -3
- reconcile/jenkins_roles.py +15 -8
- reconcile/jenkins_webhooks.py +6 -3
- reconcile/jenkins_webhooks_cleaner.py +3 -2
- reconcile/jira_watcher.py +25 -9
- reconcile/utils/jira_client.py +1 -1
- {qontract_reconcile-0.10.2.dev215.dist-info → qontract_reconcile-0.10.2.dev217.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev215.dist-info → qontract_reconcile-0.10.2.dev217.dist-info}/entry_points.txt +0 -0
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
import os
|
3
3
|
import sys
|
4
4
|
from collections.abc import (
|
5
|
+
Callable,
|
5
6
|
Iterable,
|
6
7
|
Mapping,
|
7
8
|
Sequence,
|
@@ -119,7 +120,7 @@ def _build_helm_integration_spec(
|
|
119
120
|
integration_name: str,
|
120
121
|
managed: IntegrationManagedV1,
|
121
122
|
shard_manager: IntegrationShardManager,
|
122
|
-
):
|
123
|
+
) -> HelmIntegrationSpec:
|
123
124
|
integration_spec = managed.spec.dict(by_alias=True)
|
124
125
|
shard_specs = shard_manager.build_integration_shards(integration_name, managed)
|
125
126
|
his = HelmIntegrationSpec(
|
@@ -151,7 +152,7 @@ class IntegrationsEnvironment(BaseModel):
|
|
151
152
|
|
152
153
|
def collect_integrations_environment(
|
153
154
|
integrations: Iterable[IntegrationV1],
|
154
|
-
environment_name: str,
|
155
|
+
environment_name: str | None,
|
155
156
|
shard_manager: IntegrationShardManager,
|
156
157
|
) -> list[IntegrationsEnvironment]:
|
157
158
|
int_envs: dict[str, IntegrationsEnvironment] = {}
|
@@ -210,7 +211,7 @@ def fetch_desired_state(
|
|
210
211
|
upstream: str,
|
211
212
|
image: str,
|
212
213
|
image_tag_from_ref: Mapping[str, str] | None,
|
213
|
-
):
|
214
|
+
) -> None:
|
214
215
|
for ie in integrations_environments:
|
215
216
|
oc_resources = construct_oc_resources(ie, upstream, image, image_tag_from_ref)
|
216
217
|
for r in oc_resources:
|
@@ -230,17 +231,17 @@ def filter_integrations(
|
|
230
231
|
|
231
232
|
@defer
|
232
233
|
def run(
|
233
|
-
dry_run,
|
234
|
-
environment_name,
|
234
|
+
dry_run: bool,
|
235
|
+
environment_name: str | None,
|
235
236
|
integration_runtime_meta: dict[str, IntegrationMeta],
|
236
|
-
thread_pool_size=10,
|
237
|
-
internal=
|
238
|
-
use_jump_host=True,
|
239
|
-
image_tag_from_ref=None,
|
240
|
-
upstream=None,
|
241
|
-
image=None,
|
242
|
-
defer=None,
|
243
|
-
):
|
237
|
+
thread_pool_size: int = 10,
|
238
|
+
internal: bool = False,
|
239
|
+
use_jump_host: bool = True,
|
240
|
+
image_tag_from_ref: dict[str, str] | None = None,
|
241
|
+
upstream: str | None = None,
|
242
|
+
image: str | None = None,
|
243
|
+
defer: Callable | None = None,
|
244
|
+
) -> None:
|
244
245
|
# Beware, environment_name can be empty! It's optional to set it!
|
245
246
|
# If not set, all environments should be considered.
|
246
247
|
|
@@ -269,41 +270,31 @@ def run(
|
|
269
270
|
logging.debug("Nothing to do, exiting.")
|
270
271
|
sys.exit(ExitCodes.SUCCESS)
|
271
272
|
|
272
|
-
|
273
|
-
|
273
|
+
ri, oc_map = ob.fetch_current_state(
|
274
|
+
namespaces=[
|
274
275
|
ie.namespace.dict(by_alias=True) for ie in integration_environments
|
275
276
|
],
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
if
|
285
|
-
|
286
|
-
|
287
|
-
if upstream:
|
288
|
-
use_upstream = True
|
289
|
-
fetch_args["caller"] = upstream
|
290
|
-
else:
|
291
|
-
# Not set to fetch_args on purpose, fallback for cases where caller is not yet set
|
292
|
-
use_upstream = False
|
293
|
-
upstream = UPSTREAM_DEFAULT
|
294
|
-
|
295
|
-
ri, oc_map = ob.fetch_current_state(**fetch_args)
|
296
|
-
defer(oc_map.cleanup)
|
277
|
+
thread_pool_size=thread_pool_size,
|
278
|
+
integration=QONTRACT_INTEGRATION,
|
279
|
+
integration_version=QONTRACT_INTEGRATION_VERSION,
|
280
|
+
override_managed_types=["Deployment", "StatefulSet", "CronJob", "Service"],
|
281
|
+
internal=internal,
|
282
|
+
use_jump_host=use_jump_host,
|
283
|
+
caller=upstream,
|
284
|
+
)
|
285
|
+
if defer:
|
286
|
+
defer(oc_map.cleanup)
|
297
287
|
|
298
288
|
fetch_desired_state(
|
299
|
-
integration_environments,
|
289
|
+
integration_environments,
|
290
|
+
ri,
|
291
|
+
upstream or UPSTREAM_DEFAULT,
|
292
|
+
image or IMAGE_DEFAULT,
|
293
|
+
image_tag_from_ref,
|
300
294
|
)
|
301
295
|
|
302
296
|
ob.publish_metrics(ri, QONTRACT_INTEGRATION)
|
303
|
-
|
304
|
-
ob.realize_data(dry_run, oc_map, ri, thread_pool_size, caller=upstream)
|
305
|
-
else:
|
306
|
-
ob.realize_data(dry_run, oc_map, ri, thread_pool_size)
|
297
|
+
ob.realize_data(dry_run, oc_map, ri, thread_pool_size, caller=upstream)
|
307
298
|
|
308
299
|
if ri.has_error_registered():
|
309
300
|
sys.exit(ExitCodes.ERROR)
|
reconcile/jenkins_job_builder.py
CHANGED
@@ -46,7 +46,7 @@ def init_jjb(
|
|
46
46
|
return JJB(configs, secret_reader=secret_reader, print_only=print_only)
|
47
47
|
|
48
48
|
|
49
|
-
def validate_repos_and_admins(jjb: JJB):
|
49
|
+
def validate_repos_and_admins(jjb: JJB) -> None:
|
50
50
|
jjb_repos = jjb.get_repos()
|
51
51
|
app_int_repos = queries.get_repos()
|
52
52
|
missing_repos = [r for r in jjb_repos if r not in app_int_repos]
|
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
import operator
|
3
3
|
import re
|
4
4
|
import time
|
5
|
+
from typing import Any
|
5
6
|
|
6
7
|
from reconcile import queries
|
7
8
|
from reconcile.utils.jenkins_api import JenkinsApi
|
@@ -10,11 +11,13 @@ from reconcile.utils.secret_reader import SecretReader
|
|
10
11
|
QONTRACT_INTEGRATION = "jenkins-job-builds-cleaner"
|
11
12
|
|
12
13
|
|
13
|
-
def hours_to_ms(hours):
|
14
|
+
def hours_to_ms(hours: int) -> int:
|
14
15
|
return hours * 60 * 60 * 1000
|
15
16
|
|
16
17
|
|
17
|
-
def delete_builds(
|
18
|
+
def delete_builds(
|
19
|
+
jenkins: JenkinsApi, builds_todel: list[dict[str, Any]], dry_run: bool = True
|
20
|
+
) -> None:
|
18
21
|
delete_builds_count = len(builds_todel)
|
19
22
|
for idx, build in enumerate(builds_todel, start=1):
|
20
23
|
job_name = build["job_name"]
|
@@ -35,7 +38,7 @@ def delete_builds(jenkins, builds_todel, dry_run=True):
|
|
35
38
|
logging.exception(msg)
|
36
39
|
|
37
40
|
|
38
|
-
def get_last_build_ids(builds):
|
41
|
+
def get_last_build_ids(builds: list[dict[str, Any]]) -> list[str]:
|
39
42
|
builds_to_keep = []
|
40
43
|
sorted_builds = sorted(builds, key=operator.itemgetter("timestamp"), reverse=True)
|
41
44
|
if sorted_builds:
|
@@ -49,7 +52,9 @@ def get_last_build_ids(builds):
|
|
49
52
|
return builds_to_keep
|
50
53
|
|
51
54
|
|
52
|
-
def find_builds(
|
55
|
+
def find_builds(
|
56
|
+
jenkins: JenkinsApi, job_names: list[str], rules: list[dict[str, Any]]
|
57
|
+
) -> list[dict[str, Any]]:
|
53
58
|
# Current time in ms
|
54
59
|
time_ms = time.time() * 1000
|
55
60
|
|
@@ -78,7 +83,7 @@ def find_builds(jenkins, job_names, rules):
|
|
78
83
|
return builds_found
|
79
84
|
|
80
85
|
|
81
|
-
def run(dry_run):
|
86
|
+
def run(dry_run: bool) -> None:
|
82
87
|
jenkins_instances = queries.get_jenkins_instances()
|
83
88
|
secret_reader = SecretReader(queries.get_secret_reader_settings())
|
84
89
|
|
reconcile/jenkins_job_cleaner.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
from collections.abc import Iterable
|
2
3
|
|
3
4
|
from reconcile import queries
|
4
5
|
from reconcile.jenkins_job_builder import init_jjb
|
@@ -8,7 +9,9 @@ from reconcile.utils.secret_reader import SecretReader
|
|
8
9
|
QONTRACT_INTEGRATION = "jenkins-job-cleaner"
|
9
10
|
|
10
11
|
|
11
|
-
def get_managed_job_names(
|
12
|
+
def get_managed_job_names(
|
13
|
+
job_names: Iterable[str], managed_projects: Iterable[str]
|
14
|
+
) -> list[str]:
|
12
15
|
managed_jobs = set()
|
13
16
|
for job_name in job_names:
|
14
17
|
for managed_project in managed_projects:
|
@@ -18,13 +21,13 @@ def get_managed_job_names(job_names, managed_projects):
|
|
18
21
|
return list(managed_jobs)
|
19
22
|
|
20
23
|
|
21
|
-
def get_desired_job_names(instance_name: str, secret_reader: SecretReader):
|
24
|
+
def get_desired_job_names(instance_name: str, secret_reader: SecretReader) -> list[str]:
|
22
25
|
jjb = init_jjb(secret_reader)
|
23
26
|
desired_jobs = jjb.get_all_jobs(instance_name=instance_name)[instance_name]
|
24
27
|
return [j["name"] for j in desired_jobs]
|
25
28
|
|
26
29
|
|
27
|
-
def run(dry_run):
|
30
|
+
def run(dry_run: bool) -> None:
|
28
31
|
jenkins_instances = queries.get_jenkins_instances()
|
29
32
|
secret_reader = SecretReader(queries.get_secret_reader_settings())
|
30
33
|
|
reconcile/jenkins_roles.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
from collections.abc import Iterable, Mapping
|
2
3
|
|
3
4
|
from reconcile import queries
|
4
5
|
from reconcile.utils import (
|
@@ -76,8 +77,8 @@ def get_jenkins_map() -> dict[str, JenkinsApi]:
|
|
76
77
|
return jenkins_map
|
77
78
|
|
78
79
|
|
79
|
-
def get_current_state(jenkins_map):
|
80
|
-
current_state = []
|
80
|
+
def get_current_state(jenkins_map: Mapping[str, JenkinsApi]) -> list[dict[str, str]]:
|
81
|
+
current_state: list[dict[str, str]] = []
|
81
82
|
|
82
83
|
for instance, jenkins in jenkins_map.items():
|
83
84
|
roles = jenkins.get_all_roles()
|
@@ -97,11 +98,11 @@ def get_current_state(jenkins_map):
|
|
97
98
|
return current_state
|
98
99
|
|
99
100
|
|
100
|
-
def get_desired_state():
|
101
|
+
def get_desired_state() -> list[dict[str, str]]:
|
101
102
|
gqlapi = gql.get_api()
|
102
103
|
roles: list[dict] = expiration.filter(gqlapi.query(ROLES_QUERY)["roles"])
|
103
104
|
|
104
|
-
desired_state = []
|
105
|
+
desired_state: list[dict[str, str]] = []
|
105
106
|
for r in roles:
|
106
107
|
for p in r["permissions"]:
|
107
108
|
if p["service"] != "jenkins-role":
|
@@ -128,7 +129,9 @@ def get_desired_state():
|
|
128
129
|
return desired_state
|
129
130
|
|
130
131
|
|
131
|
-
def calculate_diff(
|
132
|
+
def calculate_diff(
|
133
|
+
current_state: Iterable[dict[str, str]], desired_state: Iterable[dict[str, str]]
|
134
|
+
) -> list[dict[str, str]]:
|
132
135
|
diff = []
|
133
136
|
users_to_assign = subtract_states(
|
134
137
|
desired_state, current_state, "assign_role_to_user"
|
@@ -142,7 +145,11 @@ def calculate_diff(current_state, desired_state):
|
|
142
145
|
return diff
|
143
146
|
|
144
147
|
|
145
|
-
def subtract_states(
|
148
|
+
def subtract_states(
|
149
|
+
from_state: Iterable[dict[str, str]],
|
150
|
+
subtract_state: Iterable[dict[str, str]],
|
151
|
+
action: str,
|
152
|
+
) -> list[dict[str, str]]:
|
146
153
|
result = []
|
147
154
|
|
148
155
|
for f_user in from_state:
|
@@ -163,7 +170,7 @@ def subtract_states(from_state, subtract_state, action):
|
|
163
170
|
return result
|
164
171
|
|
165
172
|
|
166
|
-
def act(diff, jenkins_map):
|
173
|
+
def act(diff: dict[str, str], jenkins_map: Mapping[str, JenkinsApi]) -> None:
|
167
174
|
instance = diff["instance"]
|
168
175
|
role = diff["role"]
|
169
176
|
user = diff["user"]
|
@@ -177,7 +184,7 @@ def act(diff, jenkins_map):
|
|
177
184
|
raise Exception(f"invalid action: {action}")
|
178
185
|
|
179
186
|
|
180
|
-
def run(dry_run):
|
187
|
+
def run(dry_run: bool) -> None:
|
181
188
|
jenkins_map = get_jenkins_map()
|
182
189
|
current_state = get_current_state(jenkins_map)
|
183
190
|
desired_state = get_desired_state()
|
reconcile/jenkins_webhooks.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import copy
|
2
2
|
import logging
|
3
|
+
from collections.abc import Callable, MutableMapping
|
3
4
|
from typing import Any
|
4
5
|
|
5
6
|
from reconcile import queries
|
@@ -17,7 +18,9 @@ def get_gitlab_api(secret_reader: SecretReader) -> GitLabApi:
|
|
17
18
|
return GitLabApi(instance, secret_reader=secret_reader)
|
18
19
|
|
19
20
|
|
20
|
-
def get_hooks_to_add(
|
21
|
+
def get_hooks_to_add(
|
22
|
+
desired_state: MutableMapping, gl: GitLabApi
|
23
|
+
) -> MutableMapping[str, list[dict[str, Any]]]:
|
21
24
|
diff = copy.deepcopy(desired_state)
|
22
25
|
for project_url, desired_hooks in diff.items():
|
23
26
|
try:
|
@@ -45,7 +48,7 @@ def get_hooks_to_add(desired_state, gl):
|
|
45
48
|
|
46
49
|
|
47
50
|
@defer
|
48
|
-
def run(dry_run, defer=None):
|
51
|
+
def run(dry_run: bool, defer: Callable | None = None) -> None:
|
49
52
|
secret_reader = SecretReader(queries.get_secret_reader_settings())
|
50
53
|
jjb: JJB = init_jjb(secret_reader)
|
51
54
|
gl = get_gitlab_api(secret_reader)
|
@@ -63,7 +66,7 @@ def run(dry_run, defer=None):
|
|
63
66
|
gl.create_project_hook(project_url, h)
|
64
67
|
|
65
68
|
|
66
|
-
def early_exit_desired_state(*args, **kwargs) -> dict[str, Any]:
|
69
|
+
def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
67
70
|
return {
|
68
71
|
"jenkins_configs": queries.get_jenkins_configs(),
|
69
72
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
from collections.abc import Callable
|
2
3
|
from typing import Any
|
3
4
|
|
4
5
|
from reconcile import queries
|
@@ -9,7 +10,7 @@ QONTRACT_INTEGRATION = "jenkins-webhooks-cleaner"
|
|
9
10
|
|
10
11
|
|
11
12
|
@defer
|
12
|
-
def run(dry_run, defer=None):
|
13
|
+
def run(dry_run: bool, defer: Callable | None = None) -> None:
|
13
14
|
instance = queries.get_gitlab_instance()
|
14
15
|
settings = queries.get_app_interface_settings()
|
15
16
|
gl = GitLabApi(instance, settings=settings)
|
@@ -40,7 +41,7 @@ def run(dry_run, defer=None):
|
|
40
41
|
logging.warning("no access to project: " + repo)
|
41
42
|
|
42
43
|
|
43
|
-
def early_exit_desired_state(*args, **kwargs) -> dict[str, Any]:
|
44
|
+
def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
44
45
|
return {
|
45
46
|
"previous_urls": queries.get_jenkins_instances_previous_urls(),
|
46
47
|
}
|
reconcile/jira_watcher.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
import logging
|
2
|
+
from collections.abc import Callable, Mapping, Sequence
|
3
|
+
from typing import Any
|
2
4
|
|
3
5
|
from reconcile import queries
|
4
6
|
from reconcile.slack_base import slackapi_from_slack_workspace
|
@@ -6,6 +8,7 @@ from reconcile.utils.defer import defer
|
|
6
8
|
from reconcile.utils.jira_client import JiraClient
|
7
9
|
from reconcile.utils.secret_reader import SecretReader
|
8
10
|
from reconcile.utils.sharding import is_in_shard_round_robin
|
11
|
+
from reconcile.utils.slack_api import SlackApi
|
9
12
|
from reconcile.utils.state import (
|
10
13
|
State,
|
11
14
|
init_state,
|
@@ -14,7 +17,9 @@ from reconcile.utils.state import (
|
|
14
17
|
QONTRACT_INTEGRATION = "jira-watcher"
|
15
18
|
|
16
19
|
|
17
|
-
def fetch_current_state(
|
20
|
+
def fetch_current_state(
|
21
|
+
jira_board: Mapping, settings: Mapping
|
22
|
+
) -> tuple[JiraClient, dict[str, dict[str, str]]]:
|
18
23
|
jira = JiraClient(jira_board, settings=settings)
|
19
24
|
issues = jira.get_issues(fields=["key", "status", "summary"])
|
20
25
|
return jira, {
|
@@ -23,11 +28,18 @@ def fetch_current_state(jira_board, settings):
|
|
23
28
|
}
|
24
29
|
|
25
30
|
|
26
|
-
def fetch_previous_state(state, project):
|
31
|
+
def fetch_previous_state(state: State, project: str) -> dict:
|
27
32
|
return state.get(project, {})
|
28
33
|
|
29
34
|
|
30
|
-
def format_message(
|
35
|
+
def format_message(
|
36
|
+
server: str,
|
37
|
+
key: str,
|
38
|
+
data: Mapping,
|
39
|
+
event: str,
|
40
|
+
previous_state: Mapping | None = None,
|
41
|
+
current_state: Mapping | None = None,
|
42
|
+
) -> str:
|
31
43
|
summary = data["summary"]
|
32
44
|
info = (
|
33
45
|
": {} -> {}".format(previous_state["status"], current_state["status"])
|
@@ -38,7 +50,9 @@ def format_message(server, key, data, event, previous_state=None, current_state=
|
|
38
50
|
return f"{url} ({summary}) {event}{info}"
|
39
51
|
|
40
52
|
|
41
|
-
def calculate_diff(
|
53
|
+
def calculate_diff(
|
54
|
+
server: str, current_state: Mapping, previous_state: Mapping
|
55
|
+
) -> list[str]:
|
42
56
|
messages = []
|
43
57
|
new_issues = [
|
44
58
|
format_message(server, key, data, "created")
|
@@ -64,7 +78,7 @@ def calculate_diff(server, current_state, previous_state):
|
|
64
78
|
return messages
|
65
79
|
|
66
80
|
|
67
|
-
def init_slack(jira_board):
|
81
|
+
def init_slack(jira_board: Mapping[str, Any]) -> SlackApi:
|
68
82
|
secret_reader = SecretReader(queries.get_secret_reader_settings())
|
69
83
|
slack_info = jira_board["slack"]
|
70
84
|
|
@@ -77,7 +91,7 @@ def init_slack(jira_board):
|
|
77
91
|
)
|
78
92
|
|
79
93
|
|
80
|
-
def act(dry_run, jira_board, diffs):
|
94
|
+
def act(dry_run: bool, jira_board: Mapping[str, str], diffs: Sequence[str]) -> None:
|
81
95
|
if not dry_run and diffs:
|
82
96
|
slack = init_slack(jira_board)
|
83
97
|
|
@@ -87,16 +101,17 @@ def act(dry_run, jira_board, diffs):
|
|
87
101
|
slack.chat_post_message(diff)
|
88
102
|
|
89
103
|
|
90
|
-
def write_state(state: State, project, state_to_write):
|
104
|
+
def write_state(state: State, project: str, state_to_write: Mapping) -> None:
|
91
105
|
state.add(project, value=state_to_write, force=True)
|
92
106
|
|
93
107
|
|
94
108
|
@defer
|
95
|
-
def run(dry_run, defer):
|
109
|
+
def run(dry_run: bool, defer: Callable | None = None) -> None:
|
96
110
|
jira_boards = [j for j in queries.get_jira_boards() if j.get("slack")]
|
97
111
|
settings = queries.get_app_interface_settings()
|
98
112
|
state = init_state(integration=QONTRACT_INTEGRATION)
|
99
|
-
defer
|
113
|
+
if defer:
|
114
|
+
defer(state.cleanup)
|
100
115
|
for index, jira_board in enumerate(jira_boards):
|
101
116
|
if not is_in_shard_round_robin(jira_board["name"], index):
|
102
117
|
continue
|
@@ -109,6 +124,7 @@ def run(dry_run, defer):
|
|
109
124
|
continue
|
110
125
|
previous_state = fetch_previous_state(state, jira.project)
|
111
126
|
if previous_state:
|
127
|
+
assert jira.server
|
112
128
|
diffs = calculate_diff(jira.server, current_state, previous_state)
|
113
129
|
act(dry_run, jira_board, diffs)
|
114
130
|
if not dry_run:
|
reconcile/utils/jira_client.py
CHANGED
{qontract_reconcile-0.10.2.dev215.dist-info → qontract_reconcile-0.10.2.dev217.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|