qontract-reconcile 0.10.1rc1150__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.1rc1150.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc1150.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 +10 -0
- tools/test/test_qontract_cli.py +24 -0
- {qontract_reconcile-0.10.1rc1150.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc1150.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc1150.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
@@ -151,6 +151,9 @@ from reconcile.utils.state import init_state
|
|
151
151
|
from reconcile.utils.terraform_client import TerraformClient as Terraform
|
152
152
|
from tools.cli_commands.cost_report.aws import AwsCostReportCommand
|
153
153
|
from tools.cli_commands.cost_report.openshift import OpenShiftCostReportCommand
|
154
|
+
from tools.cli_commands.cost_report.openshift_cost_optimization import (
|
155
|
+
OpenShiftCostOptimizationReportCommand,
|
156
|
+
)
|
154
157
|
from tools.cli_commands.erv2 import (
|
155
158
|
Erv2Cli,
|
156
159
|
TerraformCli,
|
@@ -2772,6 +2775,13 @@ def openshift_cost_report(ctx):
|
|
2772
2775
|
print(command.execute())
|
2773
2776
|
|
2774
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
|
+
|
2775
2785
|
@get.command()
|
2776
2786
|
@click.pass_context
|
2777
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.1rc1150.dist-info → qontract_reconcile-0.10.1rc1151.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|