qontract-reconcile 0.10.1rc1149__py3-none-any.whl → 0.10.1rc1151__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.1rc1149.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc1149.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/RECORD +25 -16
- reconcile/gql_definitions/cost_report/cost_namespaces.py +2 -0
- reconcile/typed_queries/cost_report/cost_namespaces.py +7 -4
- tools/cli_commands/cost_report/aws.py +12 -25
- tools/cli_commands/cost_report/cost_management_api.py +79 -6
- tools/cli_commands/cost_report/model.py +21 -0
- tools/cli_commands/cost_report/openshift.py +7 -20
- tools/cli_commands/cost_report/openshift_cost_optimization.py +187 -0
- tools/cli_commands/cost_report/response.py +56 -1
- tools/cli_commands/cost_report/util.py +13 -0
- tools/cli_commands/cost_report/view.py +128 -2
- tools/cli_commands/test/__init__.py +0 -0
- tools/cli_commands/test/conftest.py +332 -0
- tools/cli_commands/test/test_aws_cost_report.py +258 -0
- tools/cli_commands/test/test_cost_management_api.py +326 -0
- tools/cli_commands/test/test_gpg_encrypt.py +235 -0
- tools/cli_commands/test/test_openshift_cost_optimization_report.py +255 -0
- tools/cli_commands/test/test_openshift_cost_report.py +295 -0
- tools/cli_commands/test/test_util.py +70 -0
- tools/qontract_cli.py +67 -24
- tools/test/test_qontract_cli.py +24 -0
- {qontract_reconcile-0.10.1rc1149.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc1149.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc1149.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,295 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from decimal import Decimal
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from pytest_mock import MockerFixture
|
7
|
+
|
8
|
+
from reconcile.typed_queries.cost_report.app_names import App
|
9
|
+
from reconcile.typed_queries.cost_report.cost_namespaces import (
|
10
|
+
CostNamespace,
|
11
|
+
CostNamespaceLabels,
|
12
|
+
)
|
13
|
+
from tools.cli_commands.cost_report.model import ChildAppReport, Report, ReportItem
|
14
|
+
from tools.cli_commands.cost_report.openshift import OpenShiftCostReportCommand
|
15
|
+
from tools.cli_commands.cost_report.response import OpenShiftReportCostResponse
|
16
|
+
from tools.cli_commands.test.conftest import (
|
17
|
+
COST_REPORT_SECRET,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
@pytest.fixture
|
22
|
+
def mock_gql(mocker: MockerFixture) -> Any:
|
23
|
+
return mocker.patch("tools.cli_commands.cost_report.openshift.gql")
|
24
|
+
|
25
|
+
|
26
|
+
@pytest.fixture
|
27
|
+
def mock_cost_management_api(mocker: MockerFixture) -> Any:
|
28
|
+
return mocker.patch(
|
29
|
+
"tools.cli_commands.cost_report.openshift.CostManagementApi",
|
30
|
+
autospec=True,
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
@pytest.fixture
|
35
|
+
def mock_fetch_cost_report_secret(mocker: MockerFixture) -> Any:
|
36
|
+
return mocker.patch(
|
37
|
+
"tools.cli_commands.cost_report.openshift.fetch_cost_report_secret",
|
38
|
+
return_value=COST_REPORT_SECRET,
|
39
|
+
)
|
40
|
+
|
41
|
+
|
42
|
+
def test_openshift_cost_report_create(
|
43
|
+
mock_gql: Any,
|
44
|
+
mock_cost_management_api: Any,
|
45
|
+
mock_fetch_cost_report_secret: Any,
|
46
|
+
) -> None:
|
47
|
+
openshift_cost_report_command = OpenShiftCostReportCommand.create()
|
48
|
+
|
49
|
+
assert isinstance(openshift_cost_report_command, OpenShiftCostReportCommand)
|
50
|
+
assert openshift_cost_report_command.gql_api == mock_gql.get_api.return_value
|
51
|
+
assert (
|
52
|
+
openshift_cost_report_command.cost_management_api
|
53
|
+
== mock_cost_management_api.create_from_secret.return_value
|
54
|
+
)
|
55
|
+
assert openshift_cost_report_command.thread_pool_size == 10
|
56
|
+
mock_cost_management_api.create_from_secret.assert_called_once_with(
|
57
|
+
COST_REPORT_SECRET
|
58
|
+
)
|
59
|
+
mock_fetch_cost_report_secret.assert_called_once_with(mock_gql.get_api.return_value)
|
60
|
+
|
61
|
+
|
62
|
+
@pytest.fixture
|
63
|
+
def openshift_cost_report_command(
|
64
|
+
mock_gql: Any,
|
65
|
+
mock_cost_management_api: Any,
|
66
|
+
mock_fetch_cost_report_secret: Any,
|
67
|
+
) -> OpenShiftCostReportCommand:
|
68
|
+
return OpenShiftCostReportCommand.create()
|
69
|
+
|
70
|
+
|
71
|
+
@pytest.fixture
|
72
|
+
def mock_get_app_names(mocker: MockerFixture) -> Any:
|
73
|
+
return mocker.patch("tools.cli_commands.cost_report.openshift.get_app_names")
|
74
|
+
|
75
|
+
|
76
|
+
@pytest.fixture
|
77
|
+
def mock_get_cost_namespaces(mocker: MockerFixture) -> Any:
|
78
|
+
return mocker.patch("tools.cli_commands.cost_report.openshift.get_cost_namespaces")
|
79
|
+
|
80
|
+
|
81
|
+
PARENT_APP = App(name="parent", parent_app_name=None)
|
82
|
+
CHILD_APP = App(name="child", parent_app_name="parent")
|
83
|
+
|
84
|
+
PARENT_APP_NAMESPACE = CostNamespace(
|
85
|
+
name="parent_namespace",
|
86
|
+
labels=CostNamespaceLabels(),
|
87
|
+
app_name=PARENT_APP.name,
|
88
|
+
cluster_name="parent_cluster",
|
89
|
+
cluster_external_id="parent_cluster_external_id",
|
90
|
+
)
|
91
|
+
CHILD_APP_NAMESPACE = CostNamespace(
|
92
|
+
name="child_namespace",
|
93
|
+
labels=CostNamespaceLabels(),
|
94
|
+
app_name=CHILD_APP.name,
|
95
|
+
cluster_name="child_cluster",
|
96
|
+
cluster_external_id="child_cluster_external_id",
|
97
|
+
)
|
98
|
+
|
99
|
+
|
100
|
+
def test_openshift_cost_report_execute(
|
101
|
+
openshift_cost_report_command: OpenShiftCostReportCommand,
|
102
|
+
mock_get_app_names: Any,
|
103
|
+
mock_get_cost_namespaces: Any,
|
104
|
+
fx: Callable,
|
105
|
+
) -> None:
|
106
|
+
expected_output = fx("empty_openshift_cost_report.md")
|
107
|
+
|
108
|
+
mock_get_app_names.return_value = []
|
109
|
+
mock_get_cost_namespaces.return_value = []
|
110
|
+
|
111
|
+
output = openshift_cost_report_command.execute()
|
112
|
+
|
113
|
+
assert output.rstrip() == expected_output.rstrip()
|
114
|
+
|
115
|
+
|
116
|
+
def test_openshift_cost_report_get_apps(
|
117
|
+
openshift_cost_report_command: OpenShiftCostReportCommand,
|
118
|
+
mock_get_app_names: Any,
|
119
|
+
) -> None:
|
120
|
+
expected_apps = [PARENT_APP, CHILD_APP]
|
121
|
+
mock_get_app_names.return_value = expected_apps
|
122
|
+
|
123
|
+
apps = openshift_cost_report_command.get_apps()
|
124
|
+
|
125
|
+
assert apps == expected_apps
|
126
|
+
|
127
|
+
|
128
|
+
def test_openshift_cost_report_get_cost_namespaces(
|
129
|
+
openshift_cost_report_command: OpenShiftCostReportCommand,
|
130
|
+
mock_get_cost_namespaces: Any,
|
131
|
+
) -> None:
|
132
|
+
expected_namespaces = [PARENT_APP_NAMESPACE, CHILD_APP_NAMESPACE]
|
133
|
+
mock_get_cost_namespaces.return_value = expected_namespaces
|
134
|
+
|
135
|
+
apps = openshift_cost_report_command.get_cost_namespaces()
|
136
|
+
|
137
|
+
assert apps == expected_namespaces
|
138
|
+
|
139
|
+
|
140
|
+
def openshift_report_cost_response_builder(
|
141
|
+
delta_value: int,
|
142
|
+
delta_percent: int,
|
143
|
+
total: int,
|
144
|
+
project: str,
|
145
|
+
cluster: str,
|
146
|
+
) -> OpenShiftReportCostResponse:
|
147
|
+
return OpenShiftReportCostResponse.parse_obj({
|
148
|
+
"meta": {
|
149
|
+
"delta": {
|
150
|
+
"value": delta_value,
|
151
|
+
"percent": delta_percent,
|
152
|
+
},
|
153
|
+
"total": {
|
154
|
+
"cost": {
|
155
|
+
"total": {
|
156
|
+
"value": total,
|
157
|
+
"units": "USD",
|
158
|
+
}
|
159
|
+
}
|
160
|
+
},
|
161
|
+
},
|
162
|
+
"data": [
|
163
|
+
{
|
164
|
+
"date": "2024-02",
|
165
|
+
"projects": [
|
166
|
+
{
|
167
|
+
"project": project,
|
168
|
+
"values": [
|
169
|
+
{
|
170
|
+
"delta_value": delta_value,
|
171
|
+
"delta_percent": delta_percent,
|
172
|
+
"clusters": [cluster],
|
173
|
+
"cost": {
|
174
|
+
"total": {
|
175
|
+
"value": total,
|
176
|
+
"units": "USD",
|
177
|
+
}
|
178
|
+
},
|
179
|
+
}
|
180
|
+
],
|
181
|
+
},
|
182
|
+
],
|
183
|
+
}
|
184
|
+
],
|
185
|
+
})
|
186
|
+
|
187
|
+
|
188
|
+
PARENT_APP_COST_RESPONSE = openshift_report_cost_response_builder(
|
189
|
+
delta_value=100,
|
190
|
+
delta_percent=10,
|
191
|
+
total=1100,
|
192
|
+
project=PARENT_APP_NAMESPACE.name,
|
193
|
+
cluster=PARENT_APP_NAMESPACE.cluster_name,
|
194
|
+
)
|
195
|
+
|
196
|
+
CHILD_APP_COST_RESPONSE = openshift_report_cost_response_builder(
|
197
|
+
delta_value=200,
|
198
|
+
delta_percent=10,
|
199
|
+
total=2200,
|
200
|
+
project=CHILD_APP_NAMESPACE.name,
|
201
|
+
cluster=CHILD_APP_NAMESPACE.cluster_name,
|
202
|
+
)
|
203
|
+
|
204
|
+
|
205
|
+
def test_openshift_cost_report_get_reports(
|
206
|
+
openshift_cost_report_command: OpenShiftCostReportCommand,
|
207
|
+
mock_cost_management_api: Any,
|
208
|
+
) -> None:
|
209
|
+
mocked_api = mock_cost_management_api.create_from_secret.return_value
|
210
|
+
mocked_api.get_openshift_costs_report.return_value = PARENT_APP_COST_RESPONSE
|
211
|
+
|
212
|
+
reports = openshift_cost_report_command.get_reports([PARENT_APP_NAMESPACE])
|
213
|
+
|
214
|
+
assert reports == {PARENT_APP_NAMESPACE: PARENT_APP_COST_RESPONSE}
|
215
|
+
mocked_api.get_openshift_costs_report.assert_called_once_with(
|
216
|
+
project=PARENT_APP_NAMESPACE.name,
|
217
|
+
cluster=PARENT_APP_NAMESPACE.cluster_external_id,
|
218
|
+
)
|
219
|
+
|
220
|
+
|
221
|
+
PARENT_APP_REPORT = Report(
|
222
|
+
app_name="parent",
|
223
|
+
parent_app_name=None,
|
224
|
+
child_apps=[
|
225
|
+
ChildAppReport(name="child", total=Decimal(2200)),
|
226
|
+
],
|
227
|
+
child_apps_total=Decimal(2200),
|
228
|
+
date="2024-02",
|
229
|
+
items=[
|
230
|
+
ReportItem(
|
231
|
+
name=f"{PARENT_APP_NAMESPACE.cluster_name}/{PARENT_APP_NAMESPACE.name}",
|
232
|
+
delta_value=Decimal(100),
|
233
|
+
delta_percent=10,
|
234
|
+
total=Decimal(1100),
|
235
|
+
)
|
236
|
+
],
|
237
|
+
items_total=Decimal(1100),
|
238
|
+
items_delta_value=Decimal(100),
|
239
|
+
items_delta_percent=10,
|
240
|
+
total=Decimal(3300),
|
241
|
+
)
|
242
|
+
|
243
|
+
CHILD_APP_REPORT = Report(
|
244
|
+
app_name="child",
|
245
|
+
parent_app_name="parent",
|
246
|
+
child_apps=[],
|
247
|
+
child_apps_total=Decimal(0),
|
248
|
+
date="2024-02",
|
249
|
+
items=[
|
250
|
+
ReportItem(
|
251
|
+
name=f"{CHILD_APP_NAMESPACE.cluster_name}/{CHILD_APP_NAMESPACE.name}",
|
252
|
+
delta_value=Decimal(200),
|
253
|
+
delta_percent=10,
|
254
|
+
total=Decimal(2200),
|
255
|
+
)
|
256
|
+
],
|
257
|
+
items_total=Decimal(2200),
|
258
|
+
items_delta_value=Decimal(200),
|
259
|
+
items_delta_percent=10,
|
260
|
+
total=Decimal(2200),
|
261
|
+
)
|
262
|
+
|
263
|
+
|
264
|
+
def test_openshift_cost_report_process_reports(
|
265
|
+
openshift_cost_report_command: OpenShiftCostReportCommand,
|
266
|
+
) -> None:
|
267
|
+
expected_reports = {
|
268
|
+
"parent": PARENT_APP_REPORT,
|
269
|
+
"child": CHILD_APP_REPORT,
|
270
|
+
}
|
271
|
+
|
272
|
+
reports = openshift_cost_report_command.process_reports(
|
273
|
+
apps=[PARENT_APP, CHILD_APP],
|
274
|
+
responses={
|
275
|
+
PARENT_APP_NAMESPACE: PARENT_APP_COST_RESPONSE,
|
276
|
+
CHILD_APP_NAMESPACE: CHILD_APP_COST_RESPONSE,
|
277
|
+
},
|
278
|
+
)
|
279
|
+
|
280
|
+
assert reports == expected_reports
|
281
|
+
|
282
|
+
|
283
|
+
def test_openshift_cost_report_render(
|
284
|
+
openshift_cost_report_command: OpenShiftCostReportCommand,
|
285
|
+
fx: Callable,
|
286
|
+
) -> None:
|
287
|
+
expected_output = fx("openshift_cost_report.md")
|
288
|
+
reports = {
|
289
|
+
"parent": PARENT_APP_REPORT,
|
290
|
+
"child": CHILD_APP_REPORT,
|
291
|
+
}
|
292
|
+
|
293
|
+
output = openshift_cost_report_command.render(reports)
|
294
|
+
|
295
|
+
assert output == expected_output
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from unittest.mock import create_autospec
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
from pytest_mock import MockerFixture
|
6
|
+
|
7
|
+
from reconcile.gql_definitions.common.app_interface_vault_settings import (
|
8
|
+
AppInterfaceSettingsV1,
|
9
|
+
)
|
10
|
+
from reconcile.gql_definitions.cost_report.settings import CostReportSettingsV1
|
11
|
+
from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
|
12
|
+
from reconcile.utils.gql import GqlApi
|
13
|
+
from tools.cli_commands.cost_report.util import fetch_cost_report_secret
|
14
|
+
from tools.cli_commands.test.conftest import (
|
15
|
+
COST_REPORT_SECRET,
|
16
|
+
)
|
17
|
+
|
18
|
+
VAULT_SETTINGS = AppInterfaceSettingsV1(vault=True)
|
19
|
+
COST_REPORT_SETTINGS = CostReportSettingsV1(
|
20
|
+
credentials=VaultSecret(
|
21
|
+
path="some-path",
|
22
|
+
field="all",
|
23
|
+
version=None,
|
24
|
+
format=None,
|
25
|
+
)
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
@pytest.fixture
|
30
|
+
def mock_get_app_interface_vault_settings(mocker: MockerFixture) -> Any:
|
31
|
+
return mocker.patch(
|
32
|
+
"tools.cli_commands.cost_report.util.get_app_interface_vault_settings",
|
33
|
+
return_value=VAULT_SETTINGS,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
@pytest.fixture
|
38
|
+
def mock_create_secret_reader(mocker: MockerFixture) -> Any:
|
39
|
+
mock = mocker.patch(
|
40
|
+
"tools.cli_commands.cost_report.util.create_secret_reader",
|
41
|
+
autospec=True,
|
42
|
+
)
|
43
|
+
mock.return_value.read_all_secret.return_value = COST_REPORT_SECRET
|
44
|
+
return mock
|
45
|
+
|
46
|
+
|
47
|
+
@pytest.fixture
|
48
|
+
def mock_get_cost_report_settings(mocker: MockerFixture) -> Any:
|
49
|
+
return mocker.patch(
|
50
|
+
"tools.cli_commands.cost_report.util.get_cost_report_settings",
|
51
|
+
return_value=COST_REPORT_SETTINGS,
|
52
|
+
)
|
53
|
+
|
54
|
+
|
55
|
+
def test_fetch_cost_report_secret(
|
56
|
+
mock_get_app_interface_vault_settings: Any,
|
57
|
+
mock_create_secret_reader: Any,
|
58
|
+
mock_get_cost_report_settings: Any,
|
59
|
+
) -> None:
|
60
|
+
mock_gql_api = create_autospec(GqlApi)
|
61
|
+
|
62
|
+
secret = fetch_cost_report_secret(mock_gql_api)
|
63
|
+
|
64
|
+
assert secret == COST_REPORT_SECRET
|
65
|
+
mock_get_app_interface_vault_settings.assert_called_once_with(mock_gql_api.query)
|
66
|
+
mock_get_cost_report_settings.assert_called_once_with(mock_gql_api)
|
67
|
+
mock_create_secret_reader.assert_called_once_with(use_vault=True)
|
68
|
+
mock_create_secret_reader.return_value.read_all_secret.assert_called_once_with(
|
69
|
+
COST_REPORT_SETTINGS.credentials
|
70
|
+
)
|
tools/qontract_cli.py
CHANGED
@@ -7,6 +7,7 @@ import logging
|
|
7
7
|
import os
|
8
8
|
import re
|
9
9
|
import sys
|
10
|
+
import textwrap
|
10
11
|
from collections import defaultdict
|
11
12
|
from datetime import (
|
12
13
|
UTC,
|
@@ -150,6 +151,9 @@ from reconcile.utils.state import init_state
|
|
150
151
|
from reconcile.utils.terraform_client import TerraformClient as Terraform
|
151
152
|
from tools.cli_commands.cost_report.aws import AwsCostReportCommand
|
152
153
|
from tools.cli_commands.cost_report.openshift import OpenShiftCostReportCommand
|
154
|
+
from tools.cli_commands.cost_report.openshift_cost_optimization import (
|
155
|
+
OpenShiftCostOptimizationReportCommand,
|
156
|
+
)
|
153
157
|
from tools.cli_commands.erv2 import (
|
154
158
|
Erv2Cli,
|
155
159
|
TerraformCli,
|
@@ -1372,53 +1376,70 @@ def aws_creds(ctx, account_name):
|
|
1372
1376
|
|
1373
1377
|
|
1374
1378
|
@root.command()
|
1375
|
-
@click.
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1379
|
+
@click.option(
|
1380
|
+
"--account-uid",
|
1381
|
+
help="account UID of the account that owns the bucket",
|
1382
|
+
required=True,
|
1383
|
+
)
|
1384
|
+
@click.option(
|
1385
|
+
"--source-bucket",
|
1386
|
+
help="aws bucket where the source statefile is stored",
|
1387
|
+
required=True,
|
1388
|
+
)
|
1389
|
+
@click.option(
|
1390
|
+
"--source-object-path",
|
1391
|
+
help="path in the bucket where the statefile is stored",
|
1392
|
+
required=True,
|
1393
|
+
)
|
1394
|
+
@click.option(
|
1395
|
+
"--rename",
|
1396
|
+
help="optionally rename the destination repo, otherwise keep the same name for the new location",
|
1397
|
+
)
|
1398
|
+
@click.option("--region", help="AWS region")
|
1380
1399
|
@click.option(
|
1381
1400
|
"--force/--no-force",
|
1382
1401
|
help="Force the copy even if a statefile already exists at the destination",
|
1383
1402
|
default=False,
|
1384
1403
|
)
|
1385
1404
|
@click.pass_context
|
1386
|
-
def copy_tfstate(
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
SRC should include the full filename including the extension
|
1391
|
-
|
1392
|
-
DEST should include the filename without extension.
|
1393
|
-
"""
|
1405
|
+
def copy_tfstate(
|
1406
|
+
ctx, source_bucket, source_object_path, account_uid, rename, region, force
|
1407
|
+
):
|
1394
1408
|
settings = queries.get_app_interface_settings()
|
1395
1409
|
secret_reader = SecretReader(settings=settings)
|
1396
|
-
accounts = queries.get_aws_accounts(
|
1410
|
+
accounts = queries.get_aws_accounts(uid=account_uid, terraform_state=True)
|
1397
1411
|
if not accounts:
|
1398
|
-
print(f"{
|
1412
|
+
print(f"{account_uid} not found in App-Interface.")
|
1399
1413
|
sys.exit(1)
|
1400
1414
|
account = accounts[0]
|
1401
1415
|
|
1402
|
-
|
1416
|
+
# terraform repo stores its statefiles within a "folder" in AWS S3 which is defined in App-Interface
|
1417
|
+
dest_folder = [
|
1403
1418
|
i
|
1404
1419
|
for i in account["terraformState"]["integrations"]
|
1405
1420
|
if i["integration"] == "terraform-repo"
|
1406
1421
|
]
|
1407
|
-
if
|
1422
|
+
if not dest_folder:
|
1408
1423
|
logging.error(
|
1409
|
-
"terraform-repo is missing a section in this account's '/dependencies/terraform-state-1.yml' file, please add one using the docs in https://gitlab.cee.redhat.com/service/app-interface/-/blob/master/docs/terraform-repo/
|
1424
|
+
"terraform-repo is missing a section in this account's '/dependencies/terraform-state-1.yml' file, please add one using the docs in https://gitlab.cee.redhat.com/service/app-interface/-/blob/master/docs/terraform-repo/getting-started.md?ref_type=heads#step-1-setup-aws-account and then try again"
|
1410
1425
|
)
|
1411
1426
|
return
|
1412
1427
|
|
1413
|
-
|
1428
|
+
dest_filename = ""
|
1429
|
+
if rename:
|
1430
|
+
dest_filename = rename.removesuffix(".tfstate")
|
1431
|
+
else:
|
1432
|
+
dest_filename = source_object_path.removesuffix(".tfstate")
|
1433
|
+
|
1434
|
+
dest_key = f"{dest_folder[0]['key']}/{dest_filename}-tf-repo.tfstate"
|
1414
1435
|
dest_bucket = account["terraformState"]["bucket"]
|
1415
1436
|
|
1416
1437
|
with AWSApi(1, accounts, settings, secret_reader) as aws:
|
1417
|
-
session = aws.get_session(
|
1438
|
+
session = aws.get_session(account["name"])
|
1418
1439
|
s3_client = aws.get_session_client(session, "s3", region)
|
1419
1440
|
copy_source = {
|
1420
|
-
"Bucket":
|
1421
|
-
"Key":
|
1441
|
+
"Bucket": source_bucket,
|
1442
|
+
"Key": source_object_path,
|
1422
1443
|
}
|
1423
1444
|
|
1424
1445
|
dest_pretty_path = f"s3://{dest_bucket}/{dest_key}"
|
@@ -1438,10 +1459,25 @@ def copy_tfstate(ctx, account_name, bucket, src, dest, region, force):
|
|
1438
1459
|
)
|
1439
1460
|
return
|
1440
1461
|
|
1441
|
-
prompt_text = f"Are you sure you want to copy 's3://{
|
1462
|
+
prompt_text = f"Are you sure you want to copy 's3://{source_bucket}/{source_object_path}' to '{dest_pretty_path}'?"
|
1442
1463
|
if click.confirm(prompt_text):
|
1443
1464
|
s3_client.copy(copy_source, dest_bucket, dest_key)
|
1444
|
-
|
1465
|
+
print(
|
1466
|
+
textwrap.dedent(f"""
|
1467
|
+
Nicely done! Your tfstate file has been migrated. Now you can create a repo definition in App-Interface like so:
|
1468
|
+
|
1469
|
+
---
|
1470
|
+
$schema: /aws/terraform-repo-1.yml
|
1471
|
+
|
1472
|
+
account:
|
1473
|
+
$ref: {account["path"]}
|
1474
|
+
|
1475
|
+
name: {dest_filename}
|
1476
|
+
repository: <FILL_IN>
|
1477
|
+
projectPath: <FILL_IN>
|
1478
|
+
tfVersion: <FILL_IN>
|
1479
|
+
ref: <FILL_IN>""")
|
1480
|
+
)
|
1445
1481
|
|
1446
1482
|
|
1447
1483
|
@get.command(short_help='obtain "rosa create cluster" command by cluster name')
|
@@ -2739,6 +2775,13 @@ def openshift_cost_report(ctx):
|
|
2739
2775
|
print(command.execute())
|
2740
2776
|
|
2741
2777
|
|
2778
|
+
@get.command()
|
2779
|
+
@click.pass_context
|
2780
|
+
def openshift_cost_optimization_report(ctx):
|
2781
|
+
command = OpenShiftCostOptimizationReportCommand.create()
|
2782
|
+
print(command.execute())
|
2783
|
+
|
2784
|
+
|
2742
2785
|
@get.command()
|
2743
2786
|
@click.pass_context
|
2744
2787
|
def osd_component_versions(ctx):
|
tools/test/test_qontract_cli.py
CHANGED
@@ -171,3 +171,27 @@ def test_get_openshift_cost_report(
|
|
171
171
|
assert result.output == "some report\n"
|
172
172
|
mock_openshift_cost_report_command.create.assert_called_once_with()
|
173
173
|
mock_openshift_cost_report_command.create.return_value.execute.assert_called_once_with()
|
174
|
+
|
175
|
+
|
176
|
+
@pytest.fixture
|
177
|
+
def mock_openshift_cost_optimization_report_command(mocker):
|
178
|
+
return mocker.patch(
|
179
|
+
"tools.qontract_cli.OpenShiftCostOptimizationReportCommand", autospec=True
|
180
|
+
)
|
181
|
+
|
182
|
+
|
183
|
+
def test_get_openshift_cost_optimization_report(
|
184
|
+
env_vars, mock_queries, mock_openshift_cost_optimization_report_command
|
185
|
+
):
|
186
|
+
mock_openshift_cost_optimization_report_command.create.return_value.execute.return_value = "some report"
|
187
|
+
runner = CliRunner()
|
188
|
+
result = runner.invoke(
|
189
|
+
qontract_cli.get,
|
190
|
+
"openshift-cost-optimization-report",
|
191
|
+
obj={},
|
192
|
+
)
|
193
|
+
|
194
|
+
assert result.exit_code == 0
|
195
|
+
assert result.output == "some report\n"
|
196
|
+
mock_openshift_cost_optimization_report_command.create.assert_called_once_with()
|
197
|
+
mock_openshift_cost_optimization_report_command.create.return_value.execute.assert_called_once_with()
|
{qontract_reconcile-0.10.1rc1149.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|