Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CUR proxy + CUR 2.0 - Major v4.0.0 #807

Merged
merged 105 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
cac1ad6
add cur proxy
iakov-aws Apr 30, 2024
9a5a6fc
wip
iakov-aws Apr 30, 2024
542bb20
wip
iakov-aws Apr 30, 2024
73f90b8
add more
iakov-aws Apr 30, 2024
c9dcbf5
wip
iakov-aws May 1, 2024
73ab2e3
wip
iakov-aws May 1, 2024
ea3ab1c
more fixes
iakov-aws May 2, 2024
5759257
add maps
iakov-aws May 6, 2024
47786f9
fix propagation of fields
iakov-aws May 6, 2024
30df100
add proxy cur command
iakov-aws May 6, 2024
1b52025
add sql diff and some refactoring
iakov-aws May 11, 2024
2f6e5c1
Merge branch 'main' into cur-proxy
iakov-aws May 29, 2024
7f81681
refactore account map
iakov-aws May 30, 2024
79b8de1
refactoring for account map handling
iakov-aws Jun 1, 2024
52e950e
set verbosity
iakov-aws Jun 2, 2024
4de86b5
minor fixes
iakov-aws Jun 2, 2024
16b5c2b
minor fixes
iakov-aws Jun 2, 2024
164c6e3
add doc
iakov-aws Jun 2, 2024
c3cdd3e
fix update procedure
iakov-aws Jun 3, 2024
215e9d3
add account_map_cur2.sql
iakov-aws Jun 3, 2024
f76e818
add plugin dashboard deployment
iakov-aws Jun 20, 2024
6478374
add plugin dashboard deployment
iakov-aws Jun 20, 2024
63f00e5
add plugin dashboard deployment
iakov-aws Jun 20, 2024
7268ee6
Update cid-plugin.yml
iakov-aws Jun 21, 2024
bf875e5
Merge branch 'main' into cur-proxy
iakov-aws Jun 22, 2024
5f33014
allow cur database to be different from the main one
iakov-aws Jun 23, 2024
63b04a4
minor fixes
iakov-aws Jun 23, 2024
26c725c
wip
iakov-aws Jun 25, 2024
7ba2640
filter glue catalogs
iakov-aws Jun 25, 2024
65dfe5d
filter glue catalogs
iakov-aws Jun 25, 2024
0006aab
fix account map
iakov-aws Jun 25, 2024
42eb8d9
fix the cur fetching
iakov-aws Jun 25, 2024
b33fff8
wip
iakov-aws Jun 25, 2024
6ede8f1
Merge
iakov-aws Jun 25, 2024
0ce186b
wip
iakov-aws Jun 25, 2024
fb5c228
wip
iakov-aws Jun 25, 2024
b8a2279
Merge branch 'main' into cur-proxy
iakov-aws Jun 28, 2024
e57b4e6
Updated permissions and crawler check logic (#872)
petrokashlikov Jun 29, 2024
981cbdf
Merge branch 'main' into cur-proxy
iakov-aws Jul 2, 2024
edb0d60
avoid sql issues
iakov-aws Jul 2, 2024
b69bfbc
fix cli help
iakov-aws Jul 2, 2024
3124f05
remove a warning and add done at the end of proxy command
iakov-aws Jul 3, 2024
2432b67
add sp/ri columns
iakov-aws Jul 4, 2024
a9b6cd6
WIP (#859)
petrokashlikov Jul 11, 2024
cbb363d
cleanup non ri or non sp views as with proxy we do not need them anymore
iakov-aws Jul 19, 2024
f65eb73
typo
iakov-aws Jul 19, 2024
120c9af
merge
iakov-aws Jul 26, 2024
9f7d002
switch all foundationalviews to cur2
iakov-aws Jul 27, 2024
a52db35
fix merge errors
iakov-aws Jul 27, 2024
246d99a
fix sql
iakov-aws Jul 27, 2024
2cd37d3
minor restyling of sql
iakov-aws Jul 27, 2024
68dc7e0
fixes for trend
iakov-aws Jul 27, 2024
11fbc99
rename ri-sp to default
iakov-aws Jul 27, 2024
8595faf
aling cur2 parameters
iakov-aws Jul 27, 2024
992b6d6
support mulitdb
iakov-aws Jul 31, 2024
438b9b8
merge
iakov-aws Aug 1, 2024
0e78f22
Merge branch 'main' into cur-proxy
iakov-aws Aug 1, 2024
7dcffb8
fix map update
iakov-aws Aug 12, 2024
1a2bd1b
Merge branch 'main' into cur-proxy
iakov-aws Aug 16, 2024
cb558b5
update marketplace
iakov-aws Aug 21, 2024
0278aaa
update kpi
iakov-aws Aug 21, 2024
ed3abb8
updates
iakov-aws Aug 21, 2024
6cedd84
add tags and refactor
iakov-aws Aug 22, 2024
903f9b3
reduse verbosity
iakov-aws Aug 22, 2024
c4faa51
fix cur1 to cur2 update and add some tracing
iakov-aws Aug 22, 2024
046bff3
adjustt time dates
iakov-aws Aug 23, 2024
274c612
fix ri-sp mapping
iakov-aws Aug 23, 2024
4b424ad
refactoring and fixes
iakov-aws Aug 23, 2024
4c9a29b
refactoring to speed up deployment
iakov-aws Aug 24, 2024
a791e75
fix test
iakov-aws Aug 25, 2024
73d6afc
fix test
iakov-aws Aug 25, 2024
05a390b
fix extended-support-cost-projection
iakov-aws Sep 4, 2024
64bae55
Merge branch 'main' into add-cfn-plugins
iakov-aws Sep 4, 2024
087e5fc
Merge branch 'main' into add-cfn-plugins
iakov-aws Sep 4, 2024
a59bb01
fix cfn
iakov-aws Sep 4, 2024
8067f97
wip
iakov-aws Sep 5, 2024
6b3e005
Merge branch 'main' into cur-proxy
iakov-aws Sep 5, 2024
83eae05
revert to string
iakov-aws Sep 18, 2024
4c6580d
revert savings_plan_end_time
iakov-aws Sep 18, 2024
0d46aa1
merge
iakov-aws Sep 18, 2024
1141b7a
fix mkp formatting
iakov-aws Sep 23, 2024
0d09fa6
add data provider management
iakov-aws Sep 24, 2024
28c517c
Merge branch 'main' into cur-proxy-and-dataprovider-managment
iakov-aws Sep 24, 2024
16b1dfc
more changes to support data providers and cur 2.0
iakov-aws Sep 24, 2024
4bb1fa6
Merge branch 'main' into cur-proxy
iakov-aws Sep 24, 2024
da415d5
Merge branch 'cur-proxy-and-dataprovider-managment' into cur-proxy
iakov-aws Sep 24, 2024
bc359a9
bump version
iakov-aws Sep 24, 2024
499bb38
better wording for cur legacy path
iakov-aws Sep 25, 2024
7ab19dc
create region map
iakov-aws Sep 25, 2024
0ce7dc7
Merge branch 'main' into cur-proxy
iakov-aws Sep 25, 2024
05e088c
better error management
iakov-aws Sep 29, 2024
88ddaea
support more then 254 tags
iakov-aws Sep 29, 2024
80387c2
make the right partitions
iakov-aws Sep 29, 2024
b35ac1f
make linter happier
iakov-aws Sep 29, 2024
0ff02bb
Merge branch 'main' into add-cfn-plugins
iakov-aws Sep 29, 2024
eb322dd
merge
iakov-aws Oct 11, 2024
2a19f1c
minor change to view to trigger update
iakov-aws Oct 11, 2024
688c9a3
avoid using the same bucket
iakov-aws Oct 11, 2024
6604099
Merge branch 'main' into cur-proxy
iakov-aws Oct 20, 2024
5943ab6
allow keeping legacy cur
iakov-aws Oct 20, 2024
17fa7a2
merge
iakov-aws Oct 24, 2024
9be9dfc
merge
iakov-aws Nov 5, 2024
0f362dd
Merge branch 'add-cfn-plugins' into cur-proxy
iakov-aws Nov 5, 2024
32dcdef
make linter happier
iakov-aws Nov 5, 2024
2748973
make tests more robust
iakov-aws Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions cid/builtin/core/data/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ views:
File: cid/summary_view.sql
dependsOn:
cur:
- year
- month
- bill_billing_entity
- bill_billing_period_start_date
- bill_invoice_id
Expand Down Expand Up @@ -332,7 +334,7 @@ views:
compute_savings_plan_eligible_spend:
File: cid/compute_savings_plan_eligible_spend.sql
dependsOn:
cur: true
cur:
- bill_billing_period_start_date
- bill_payer_account_id
- line_item_line_item_type
Expand Down Expand Up @@ -380,9 +382,9 @@ views:
- savings_plan_savings_plan_a_r_n

hourly_view:
spriFile: cudos/hourly_view_sp_ri.sql
spFile: cudos/hourly_view_sp.sql
riFile: cudos/hourly_view_ri.sql
#spriFile: cudos/hourly_view_sp_ri.sql
#spFile: cudos/hourly_view_sp.sql
#riFile: cudos/hourly_view_ri.sql
File: cudos/hourly_view.sql
dependsOn:
cur:
Expand Down
65 changes: 39 additions & 26 deletions cid/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from cid.plugin import Plugin
from cid.utils import get_parameter, get_parameters, set_parameters, unset_parameter, get_yesno_parameter, cid_print, isatty, merge_objects, IsolatedParameters
from cid.helpers.account_map import AccountMap
from cid.helpers import Athena, S3, IAM, CUR, Glue, QuickSight, Dashboard, Dataset, Datasource, csv2view, Organizations
from cid.helpers import Athena, S3, IAM, CUR, ProxyCUR, Glue, QuickSight, Dashboard, Dataset, Datasource, csv2view, Organizations
from cid.helpers.quicksight.template import Template as CidQsTemplate
from cid._version import __version__
from cid.export import export_analysis
Expand Down Expand Up @@ -110,6 +110,14 @@ def organizations(self) -> Organizations:
def s3(self) -> S3:
return S3(self.base.session)

@cached_property
def cur1(self):
return ProxyCUR(self.cur, target_cur_version='1')

@cached_property
def cur2(self):
return ProxyCUR(self.cur, target_cur_version='2')

@property
def cur(self) -> CUR:
if not self._clients.get('cur'):
Expand All @@ -131,6 +139,7 @@ def cur(self) -> CUR:
self._clients['cur'] = _cur
break
except CidCritical as exc:
logger.exception(exc)
cid_print(f'CUR not found in {self.athena.DatabaseName}. If you have S3 bucket with CUR in this account you can create a CUR table with Crawler.')
self.create_cur_table()
return self._clients['cur']
Expand All @@ -141,7 +150,6 @@ def accountMap(self) -> AccountMap:
_account_map = AccountMap(self.base.session)
_account_map.athena = self.athena
_account_map.cur = self.cur

self._clients.update({
'accountMap': _account_map
})
Expand Down Expand Up @@ -1356,6 +1364,7 @@ def create_or_update_dataset(self, dataset_definition: dict, dataset_id: str=Non
data = self.get_data_from_definition('dataset', dataset_definition)
template = Template(json.dumps(data))
cur_required = dataset_definition.get('dependsOn', dict()).get('cur')
cur2_required = dataset_definition.get('dependsOn', dict()).get('cur2')
athena_datasource = None

# Manage datasource
Expand Down Expand Up @@ -1447,7 +1456,8 @@ def create_or_update_dataset(self, dataset_definition: dict, dataset_id: str=Non

# Check for required views
_views = dataset_definition.get('dependsOn', {}).get('views', [])
required_views = [(self.cur.table_name if cur_required and name =='${cur_table_name}' else name) for name in _views]
#FIXME: delete this : required_views = [(self.cur.table_name if cur_required and name =='${cur_table_name}' else name) for name in _views]
required_views = _views

self.athena.discover_views(required_views)
found_views = utils.intersection(required_views, self.athena._metadata.keys())
Expand All @@ -1456,9 +1466,9 @@ def create_or_update_dataset(self, dataset_definition: dict, dataset_id: str=Non
if recursive:
print(f"Detected views: {', '.join(found_views)}")
for view_name in found_views:
if cur_required and view_name == self.cur.table_name:
logger.debug(f'Dependency view {view_name} is a CUR. Skip.')
continue
#if cur_required and view_name == self.cur.table_name:
# logger.debug(f'Dependency view {view_name} is a CUR. Skip.')
# continue
if view_name == 'account_map':
logger.debug(f'Dependency view is {view_name}. Skip.')
continue
Expand All @@ -1477,7 +1487,8 @@ def create_or_update_dataset(self, dataset_definition: dict, dataset_id: str=Non
columns_tpl = {
'athena_datasource_arn': athena_datasource.arn,
'athena_database_name': self.athena.DatabaseName,
'cur_table_name': self.cur.table_name if cur_required else None
'cur_table_name': self.cur1.table_name if cur_required else None,
'cur2_table_name': self.cur2.table_name if cur2_required else None,
}

logger.debug(f'dataset_id={dataset_id}')
Expand Down Expand Up @@ -1507,7 +1518,7 @@ def create_or_update_dataset(self, dataset_definition: dict, dataset_id: str=Non
elif found_dataset.name != compiled_dataset.get('Name'):
print(f"Dataset found with name {found_dataset.name}, but {compiled_dataset.get('Name')} expected. Updating.")
update_dataset = True
if update_dataset and get_parameters().get('on-drift', 'show').lower() != 'override' and isatty() and not cur_required:
if update_dataset and get_parameters().get('on-drift', 'show').lower() != 'override' and isatty() and not cur_required and not cur2_required:
while True:
diff = self.qs.dataset_diff(found_dataset.raw, compiled_dataset)
if diff and diff['diff']:
Expand Down Expand Up @@ -1588,17 +1599,17 @@ def create_or_update_view(self, view_name: str, recursive: bool=True, update: bo
dependencies = view_definition.get('dependsOn', {})

# Process CUR columns
if isinstance(dependencies.get('cur'), list):
for column in dependencies.get('cur'):
self.cur.ensure_column(column)
elif isinstance(dependencies.get('cur'), dict):
for column, column_type in dependencies.get('cur').items():
self.cur.ensure_column(column, column_type)
if dependencies.get('cur'):
self.cur1.ensure_columns(dependencies.get('cur'))
if dependencies.get('cur2'):
self.cur2.ensure_columns(dependencies.get('cur2'))

if recursive:
dependency_views = dependencies.get('views', [])
if 'cur' in dependency_views:
dependency_views.remove('cur')
if 'cur2' in dependency_views:
dependency_views.remove('cur2')
# Discover dependency views (may not be discovered earlier)
self.athena.discover_views(dependency_views)
logger.info(f"Dependency views: {', '.join(dependency_views)}" if dependency_views else 'No dependency views')
Expand Down Expand Up @@ -1727,17 +1738,18 @@ def get_view_query(self, view_name: str) -> str:
# View path
view_definition = self.get_definition("view", name=view_name)
cur_required = view_definition.get('dependsOn', dict()).get('cur')
if cur_required and self.cur.has_savings_plans and self.cur.has_reservations and view_definition.get('spriFile'):
view_definition['File'] = view_definition.get('spriFile')
elif cur_required and self.cur.has_savings_plans and view_definition.get('spFile'):
view_definition['File'] = view_definition.get('spFile')
elif cur_required and self.cur.has_reservations and view_definition.get('riFile'):
view_definition['File'] = view_definition.get('riFile')
elif view_definition.get('File') or view_definition.get('Data') or view_definition.get('data'):
pass
else:
logger.critical(f'\nCannot find view {view_name}. View information is incorrect, please check resources.yaml')
raise Exception(f'\nCannot find view {view_name}')
cur2_required = view_definition.get('dependsOn', dict()).get('cur2')
#if cur_required and self.cur.has_savings_plans and self.cur.has_reservations and view_definition.get('spriFile'):
# view_definition['File'] = view_definition.get('spriFile')
#elif cur_required and self.cur.has_savings_plans and view_definition.get('spFile'):
# view_definition['File'] = view_definition.get('spFile')
#elif cur_required and self.cur.has_reservations and view_definition.get('riFile'):
# view_definition['File'] = view_definition.get('riFile')
#if view_definition.get('File') or view_definition.get('Data') or view_definition.get('data'):
# pass
#else:
# logger.critical(f'\nCannot find view {view_name}. View information is incorrect, please check resources.yaml')
# raise Exception(f'\nCannot find view {view_name}')

# Load TPL file
data = self.get_data_from_definition('view', view_definition)
Expand All @@ -1748,7 +1760,8 @@ def get_view_query(self, view_name: str) -> str:

# Prepare template parameters
columns_tpl = {
'cur_table_name': self.cur.table_name if cur_required else None,
'cur_table_name': self.cur1.table_name if cur_required else None,
'cur2_table_name': self.cur2.table_name if cur2_required else None,
'athenaTableName': view_name,
'athena_database_name': self.athena.DatabaseName,
}
Expand Down
5 changes: 4 additions & 1 deletion cid/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
from cid.helpers.s3 import S3
from cid.helpers.athena import Athena
from cid.helpers.iam import IAM
from cid.helpers.cur import CUR
from cid.helpers.cur import CUR, ProxyCUR
from cid.helpers.diff import diff
from cid.helpers.quicksight import QuickSight, Dashboard, Dataset, Datasource, Template
from cid.helpers.csv2view import csv2view
from cid.helpers.organizations import Organizations
from cid.helpers.cur_proxy import ProxyView

__all__ = [
"Athena",
"S3",
"IAM",
"CUR",
"ProxyCUR",
"Glue",
"QuickSight",
"Dashboard",
Expand All @@ -22,4 +24,5 @@
"diff",
"csv2view",
"Organizations",
"ProxyView",
]
38 changes: 27 additions & 11 deletions cid/helpers/account_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def accounts(self) -> dict:
@lru_cache(1000)
def detect_metadata_table(self, name):
""" detect meta table with the list of accounts """
cid_print('Autodiscovering metadata table')
cid_print('Autodiscover metadata table')

# FIXME: This will only work for current Athena Database. We might want to check optimization_data base as well
tables = self.athena.list_table_metadata()
Expand Down Expand Up @@ -142,19 +142,35 @@ def create(self, name) -> bool:
if get_parameters().get('account-map-source'):
raise CidError('Skipping autodiscovery')

# try to find a table with data about accounts
self._athena_table_name = self.detect_metadata_table(name)
if not self._athena_table_name:
raise CidError('Metadata table not found')

# Query path
view_definition = self.athena._resources.get('views').get(name, {})
if view_definition.get('File'):
view_file = view_definition.get('File')
template = Template(resource_string(view_definition.get('providedBy'), f'data/queries/{view_file}').decode('utf-8'))
elif view_definition.get('data'):
template = Template(str(view_definition.get('data')))
raise CidError('Metadata table not found') # catch you later

if self.cur.version.startswith('2'):
template_str = '''
CREATE OR REPLACE VIEW ${athena_view_name} AS
SELECT DISTINCT
line_item_usage_account_id account_id,
MAX_BY(bill_payer_account_id, line_item_usage_start_date) parent_account_id,
MAX_BY(line_item_usage_account_name, line_item_usage_start_date) account_name,
MAX_BY(bill_payer_account_name, line_item_usage_start_date) parent_account_name
FROM
"${cur_table_name}"
GROUP BY
line_item_usage_account_id
'''
template = Template(template_str)
else:
raise CidError(f'{name} definition does not contain File or data: {view_definition}')
# Query path
view_definition = self.athena._resources.get('views').get(name, {})
if view_definition.get('File'):
view_file = view_definition.get('File')
template = Template(resource_string(view_definition.get('providedBy'), f'data/queries/{view_file}').decode('utf-8'))
elif view_definition.get('data'):
template = Template(str(view_definition.get('data')))
else:
raise CidError(f'{name} definition does not contain File or data: {view_definition}')

# Fill in TPLs
columns_tpl = {
Expand Down
4 changes: 2 additions & 2 deletions cid/helpers/athena.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def execute_query(self, sql_query, sleep_duration=1, database: str=None, catalog
raise CidCritical(f'InvalidRequestException: {exc}') from exc
except Exception as exc:
logger.debug(f'Full query: {sql_query}')
raise CidCritical(f'Athena query failed: {exc}') from exc
raise CidCritical(f'Query:\n{sql_query}\n\nAthena query failed: {exc}') from exc

current_status = query_status['QueryExecution']['Status']['State']

Expand All @@ -330,7 +330,7 @@ def execute_query(self, sql_query, sleep_duration=1, database: str=None, catalog
logger.info(f'Athena query failed: {failure_reason}')
logger.debug(f'Full query: {sql_query}')
if fail:
raise CidCritical(f'Athena query failed: {failure_reason}')
raise CidCritical(f'Query:\n{sql_query}\n\nAthena query status failed : {failure_reason}')
return False

def get_query_results(self, query_id):
Expand Down
Loading
Loading