cid-cmd 4.3.3__tar.gz → 4.3.6__tar.gz
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.
- {cid_cmd-4.3.3/cid_cmd.egg-info → cid_cmd-4.3.6}/PKG-INFO +11 -11
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/README.md +10 -10
- cid_cmd-4.3.6/cid/__init__.py +6 -0
- cid_cmd-4.3.6/cid/_version.py +2 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/resources.yaml +0 -12
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/cli.py +1 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/common.py +49 -31
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/export.py +9 -3
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/account_map.py +5 -9
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/cur.py +23 -13
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/__init__.py +10 -13
- cid_cmd-4.3.6/cid/plugin.py +62 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6/cid_cmd.egg-info}/PKG-INFO +11 -11
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid_cmd.egg-info/SOURCES.txt +0 -3
- cid_cmd-4.3.3/cid/__init__.py +0 -3
- cid_cmd-4.3.3/cid/_version.py +0 -2
- cid_cmd-4.3.3/cid/builtin/core/data/datasets/tao/dataset.json +0 -1684
- cid_cmd-4.3.3/cid/builtin/core/data/queries/tao/glue_table.json +0 -982
- cid_cmd-4.3.3/cid/builtin/core/data/queries/tao/ta_org_view.sql +0 -247
- cid_cmd-4.3.3/cid/plugin.py +0 -70
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/LICENSE +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/MANIFEST.in +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/base.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/__init__.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/__init__.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/cid/compute.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/cid/ec2_running_cost.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/cid/s3_view.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/cid/summary_view.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/co/dataset.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/cudos/hourly_view.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/cudos/resource_view.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/kpi/kpi_ebs_snap.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/kpi/kpi_ebs_storage_all.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/kpi/kpi_instance_all.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/kpi/kpi_s3_storage_all.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/kpi/kpi_tracker.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/shared/customer_all.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/trends/daily_anomaly_detection.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/trends/monthly_anomaly_detection.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/datasets/trends/monthly_bill_by_account.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/permissions/dashboard_permissions.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/permissions/dashboard_permissions_namespace.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/permissions/data_set_permissions.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/permissions/data_set_rls_permissions.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/permissions/data_source_permissions.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/permissions/folder_permissions.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/cid/compute_savings_plan_eligible_spend.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/cid/ec2_running_cost.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/cid/ri_sp_mapping.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/cid/s3.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/cid/summary_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/all_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/auto_scale.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/auto_scale_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/ebs_volume.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/ebs_volume_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/ec2_instance.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/ec2_instance_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/ecs_service.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/ecs_service_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/idle.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/idle_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/lambda.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/lambda_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/license.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/license_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/rds_database.json +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/rds_instance_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/co/rds_storage_options.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/cudos/hourly_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/cudos/resource_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/kpi/first_kpi_instance_mapping_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/kpi/kpi_ebs_snap_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/kpi/kpi_ebs_storage_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/kpi/kpi_instance_all_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/kpi/kpi_s3_storage_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/kpi/last_kpi_tracker_view.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/account_map.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/account_map_cur2.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/account_map_dummy.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/aws_accounts.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/aws_regions.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/aws_service_category_map.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/business_units_map.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/cur.yaml +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/customer_all_unlimited.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/payer_account_name_map.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/shared/ta_descriptions.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/trends/daily_anomaly_detection.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/trends/monthly_anomaly_detection.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/builtin/core/data/queries/trends/monthly_bill_by_account.sql +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/commands/__init__.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/commands/command_base.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/commands/init_qs.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/exceptions.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/__init__.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/athena.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/cloudformation.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/csv2view.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/cur_proxy.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/diff.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/glue.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/iam.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/organizations.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/parameter_store.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/dashboard.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/dashboard_patching.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/dataset.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/datasource.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/definition.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/resource.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/template.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/quicksight/version.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/randtime.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/s3.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/helpers/timezone.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/logger.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/test/python/test_csv2view.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/test/python/test_isolated_parameters.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/test/python/test_merge.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid/utils.py +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid_cmd.egg-info/dependency_links.txt +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid_cmd.egg-info/entry_points.txt +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid_cmd.egg-info/requires.txt +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/cid_cmd.egg-info/top_level.txt +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/pyproject.toml +0 -0
- {cid_cmd-4.3.3 → cid_cmd-4.3.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cid-cmd
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.6
|
|
4
4
|
Summary: Cloud Intelligence Dashboards deployment helper tool
|
|
5
5
|
Home-page: https://github.com/aws-samples/aws-cudos-framework-deployment
|
|
6
6
|
Author: AWS CUDOS Team
|
|
@@ -47,7 +47,7 @@ Dynamic: license-file
|
|
|
47
47
|
1. [Notices](#Notices)
|
|
48
48
|
|
|
49
49
|
## Overview
|
|
50
|
-
The Cloud Intelligence Dashboards is an open-source framework, lovingly cultivated and maintained by a group of customer-obsessed AWSers, that gives customers the power to get high-level and granular insight into their cost and usage data. Supported by the Well-Architected framework, the dashboards can be deployed by any customer using a CloudFormation template or a command-line tool in their environment in under 30 minutes. These dashboards help you to drive financial accountability, optimize cost, track usage goals, implement best
|
|
50
|
+
The Cloud Intelligence Dashboards is an open-source framework, lovingly cultivated and maintained by a group of customer-obsessed AWSers, that gives customers the power to get high-level and granular insight into their cost and usage data. Supported by the Well-Architected framework, the dashboards can be deployed by any customer using a CloudFormation template or a command-line tool in their environment in under 30 minutes. These dashboards help you to drive financial accountability, optimize cost, track usage goals, implement best practices for governance, and achieve operational excellence across all your organization.
|
|
51
51
|
|
|
52
52
|
Cloud Intelligence Dashboards Framework provides AWS customers with [more then 20 Dashboards](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/dashboards.html).
|
|
53
53
|
* Foundational Dashboards - A set of main Dashboards that only require Cost and Usage Report(CUR)
|
|
@@ -81,7 +81,7 @@ This foundational architecture is recommended for starting and allows deployment
|
|
|
81
81
|
5. User Teams (Executives, FinOps, Engineers) can access Cloud Intelligence Dashboards in [Amazon QuickSight](https://aws.amazon.com/quicksight/). Access is secured through [AWS IAM](https://aws.amazon.com/iam/), IIC ([AWS IAM Identity Center](https://aws.amazon.com/iam/identity-center/), formerly SSO), and optional [Row Level Security](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/row-level-security.html).
|
|
82
82
|
6. Optionally, the Advanced data collection can be deployed to enable advanced dashboards based on [AWS Trusted Advisor](https://aws.amazon.com/premiumsupport/trustedadvisor/), [AWS Health](https://aws.amazon.com/premiumsupport/technology/aws-health-dashboard/) Events and other sources. Additional data is retrieved from [AWS Organizations](https://aws.amazon.com/organizations/) or Linked Accounts. In this case [Amazon EventBridge](https://aws.amazon.com/eventbridge/) rule triggers an [AWS Step Functions](https://aws.amazon.com/step-functions/) workflow for data collection modules on a configurable schedule.
|
|
83
83
|
7. The "Account Collector" [AWS Lambda](https://aws.amazon.com/lambda/) in AWS Step Functions retrieves linked account details using AWS Organizations API.
|
|
84
|
-
8. The "Data Collection" Lambda function in AWS Step Functions assumes role in each linked account to retrieve account-specific data via [AWS SDK](https://aws.amazon.com/developer/tools/).
|
|
84
|
+
8. The "Data Collection" Lambda function in AWS Step Functions assumes a role in each linked account to retrieve account-specific data via [AWS SDK](https://aws.amazon.com/developer/tools/).
|
|
85
85
|
9. Retrieved data is stored in a centralized Amazon S3 Bucket.
|
|
86
86
|
10. [Advanced Cloud Intelligence Dashboards](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/dashboards.html#advanced-dashboards) leverage Amazon Athena and Amazon QuickSight for comprehensive data analysis.
|
|
87
87
|
|
|
@@ -102,16 +102,16 @@ The following table provides a sample cost breakdown for deploying of Foundation
|
|
|
102
102
|
| **Total Estimated Monthly Cost** | | **$100-$200** |
|
|
103
103
|
|
|
104
104
|
\* Costs are relative to the size of your Cost and Usage Report (CUR) data
|
|
105
|
-
\** Costs are relative to number of
|
|
105
|
+
\** Costs are relative to the number of users
|
|
106
106
|
|
|
107
107
|
**Additional Notes:**
|
|
108
108
|
- Free trial available for 30 days for 4 QuickSight users
|
|
109
109
|
- Actual costs may vary based on specific usage and data volume
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Please use the AWS Pricing Calculator for precise estimation.
|
|
112
112
|
|
|
113
113
|
## Prerequisites
|
|
114
|
-
You need access to AWS Accounts. We recommend deployment of the Dashboards in a dedicated Data Collection Account, other than your Management (Payer) Account. We provide
|
|
114
|
+
You need access to AWS Accounts. We recommend deployment of the Dashboards in a dedicated Data Collection Account, other than your Management (Payer) Account. We provide CloudFormation templates to copy CUR 2.0 data from your Management Account to a dedicated one. You can use it to aggregate data from multiple Management (Payer) Accounts or multiple Linked Accounts.
|
|
115
115
|
|
|
116
116
|
If you do not have access to the Management/Payer Account, you can still collect the data across multiple Linked accounts using the same approach.
|
|
117
117
|
|
|
@@ -119,7 +119,7 @@ The ownership of CID is usually with the FinOps team, who do not have administra
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
## Regions
|
|
122
|
-
Make sure you are installing data collection in the same region where you are going to use the data to avoid cross
|
|
122
|
+
Make sure you are installing data collection in the same region where you are going to use the data to avoid cross-region charges.
|
|
123
123
|
CFN deployment is only available in a limited number of regions, while CLI deployment is region agnostic.
|
|
124
124
|
|
|
125
125
|
| Region Name | Region Code | Support CLI | Support CFN |
|
|
@@ -151,7 +151,7 @@ CFN deployment is only available in a limited number of regions, while CLI deplo
|
|
|
151
151
|
|
|
152
152
|
## Deployment Steps
|
|
153
153
|
There are several ways we can deploy dashboards:
|
|
154
|
-
1. Using cid-cmd tool from command line
|
|
154
|
+
1. Using cid-cmd tool from the command line
|
|
155
155
|
1. [CloudFormation Template](./cfn-templates/cid-cfn.yml) using cid-cmd tool in Amazon Lambda. (Recommended)
|
|
156
156
|
|
|
157
157
|
Please refer to the deployment documentation [here](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/deployment-in-global-regions.html).
|
|
@@ -166,8 +166,8 @@ Please refer to the documentation [here](https://docs.aws.amazon.com/guidance/la
|
|
|
166
166
|
Please refer to the documentation [here](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/faq.html).
|
|
167
167
|
|
|
168
168
|
## Changelogs
|
|
169
|
-
For dashboards please check
|
|
170
|
-
For CID deployment tool, including
|
|
169
|
+
For dashboards please check change log [here](changes/)
|
|
170
|
+
For CID deployment tool, including CLI and CFN please check [Releases](/releases)
|
|
171
171
|
|
|
172
172
|
## Feedback
|
|
173
173
|
Please reference to [this page](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/feedback-support.html)
|
|
@@ -184,4 +184,4 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform
|
|
|
184
184
|
This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file.
|
|
185
185
|
|
|
186
186
|
## Notices
|
|
187
|
-
Dashboards and their content: (a) are for informational purposes only, (b)
|
|
187
|
+
Dashboards and their content: (a) are for informational purposes only, (b) represent current AWS product offerings and practices, which are subject to change without notice, and (c) does not create any commitments or assurances from AWS and its affiliates, suppliers or licensors. AWS content, products or services are provided “as is” without warranties, representations, or conditions of any kind, whether express or implied. The responsibilities and liabilities of AWS to its customers are controlled by AWS agreements, and this document is not part of, nor does it modify, any agreement between AWS and its customers.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
1. [Notices](#Notices)
|
|
20
20
|
|
|
21
21
|
## Overview
|
|
22
|
-
The Cloud Intelligence Dashboards is an open-source framework, lovingly cultivated and maintained by a group of customer-obsessed AWSers, that gives customers the power to get high-level and granular insight into their cost and usage data. Supported by the Well-Architected framework, the dashboards can be deployed by any customer using a CloudFormation template or a command-line tool in their environment in under 30 minutes. These dashboards help you to drive financial accountability, optimize cost, track usage goals, implement best
|
|
22
|
+
The Cloud Intelligence Dashboards is an open-source framework, lovingly cultivated and maintained by a group of customer-obsessed AWSers, that gives customers the power to get high-level and granular insight into their cost and usage data. Supported by the Well-Architected framework, the dashboards can be deployed by any customer using a CloudFormation template or a command-line tool in their environment in under 30 minutes. These dashboards help you to drive financial accountability, optimize cost, track usage goals, implement best practices for governance, and achieve operational excellence across all your organization.
|
|
23
23
|
|
|
24
24
|
Cloud Intelligence Dashboards Framework provides AWS customers with [more then 20 Dashboards](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/dashboards.html).
|
|
25
25
|
* Foundational Dashboards - A set of main Dashboards that only require Cost and Usage Report(CUR)
|
|
@@ -53,7 +53,7 @@ This foundational architecture is recommended for starting and allows deployment
|
|
|
53
53
|
5. User Teams (Executives, FinOps, Engineers) can access Cloud Intelligence Dashboards in [Amazon QuickSight](https://aws.amazon.com/quicksight/). Access is secured through [AWS IAM](https://aws.amazon.com/iam/), IIC ([AWS IAM Identity Center](https://aws.amazon.com/iam/identity-center/), formerly SSO), and optional [Row Level Security](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/row-level-security.html).
|
|
54
54
|
6. Optionally, the Advanced data collection can be deployed to enable advanced dashboards based on [AWS Trusted Advisor](https://aws.amazon.com/premiumsupport/trustedadvisor/), [AWS Health](https://aws.amazon.com/premiumsupport/technology/aws-health-dashboard/) Events and other sources. Additional data is retrieved from [AWS Organizations](https://aws.amazon.com/organizations/) or Linked Accounts. In this case [Amazon EventBridge](https://aws.amazon.com/eventbridge/) rule triggers an [AWS Step Functions](https://aws.amazon.com/step-functions/) workflow for data collection modules on a configurable schedule.
|
|
55
55
|
7. The "Account Collector" [AWS Lambda](https://aws.amazon.com/lambda/) in AWS Step Functions retrieves linked account details using AWS Organizations API.
|
|
56
|
-
8. The "Data Collection" Lambda function in AWS Step Functions assumes role in each linked account to retrieve account-specific data via [AWS SDK](https://aws.amazon.com/developer/tools/).
|
|
56
|
+
8. The "Data Collection" Lambda function in AWS Step Functions assumes a role in each linked account to retrieve account-specific data via [AWS SDK](https://aws.amazon.com/developer/tools/).
|
|
57
57
|
9. Retrieved data is stored in a centralized Amazon S3 Bucket.
|
|
58
58
|
10. [Advanced Cloud Intelligence Dashboards](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/dashboards.html#advanced-dashboards) leverage Amazon Athena and Amazon QuickSight for comprehensive data analysis.
|
|
59
59
|
|
|
@@ -74,16 +74,16 @@ The following table provides a sample cost breakdown for deploying of Foundation
|
|
|
74
74
|
| **Total Estimated Monthly Cost** | | **$100-$200** |
|
|
75
75
|
|
|
76
76
|
\* Costs are relative to the size of your Cost and Usage Report (CUR) data
|
|
77
|
-
\** Costs are relative to number of
|
|
77
|
+
\** Costs are relative to the number of users
|
|
78
78
|
|
|
79
79
|
**Additional Notes:**
|
|
80
80
|
- Free trial available for 30 days for 4 QuickSight users
|
|
81
81
|
- Actual costs may vary based on specific usage and data volume
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
Please use the AWS Pricing Calculator for precise estimation.
|
|
84
84
|
|
|
85
85
|
## Prerequisites
|
|
86
|
-
You need access to AWS Accounts. We recommend deployment of the Dashboards in a dedicated Data Collection Account, other than your Management (Payer) Account. We provide
|
|
86
|
+
You need access to AWS Accounts. We recommend deployment of the Dashboards in a dedicated Data Collection Account, other than your Management (Payer) Account. We provide CloudFormation templates to copy CUR 2.0 data from your Management Account to a dedicated one. You can use it to aggregate data from multiple Management (Payer) Accounts or multiple Linked Accounts.
|
|
87
87
|
|
|
88
88
|
If you do not have access to the Management/Payer Account, you can still collect the data across multiple Linked accounts using the same approach.
|
|
89
89
|
|
|
@@ -91,7 +91,7 @@ The ownership of CID is usually with the FinOps team, who do not have administra
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
## Regions
|
|
94
|
-
Make sure you are installing data collection in the same region where you are going to use the data to avoid cross
|
|
94
|
+
Make sure you are installing data collection in the same region where you are going to use the data to avoid cross-region charges.
|
|
95
95
|
CFN deployment is only available in a limited number of regions, while CLI deployment is region agnostic.
|
|
96
96
|
|
|
97
97
|
| Region Name | Region Code | Support CLI | Support CFN |
|
|
@@ -123,7 +123,7 @@ CFN deployment is only available in a limited number of regions, while CLI deplo
|
|
|
123
123
|
|
|
124
124
|
## Deployment Steps
|
|
125
125
|
There are several ways we can deploy dashboards:
|
|
126
|
-
1. Using cid-cmd tool from command line
|
|
126
|
+
1. Using cid-cmd tool from the command line
|
|
127
127
|
1. [CloudFormation Template](./cfn-templates/cid-cfn.yml) using cid-cmd tool in Amazon Lambda. (Recommended)
|
|
128
128
|
|
|
129
129
|
Please refer to the deployment documentation [here](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/deployment-in-global-regions.html).
|
|
@@ -138,8 +138,8 @@ Please refer to the documentation [here](https://docs.aws.amazon.com/guidance/la
|
|
|
138
138
|
Please refer to the documentation [here](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/faq.html).
|
|
139
139
|
|
|
140
140
|
## Changelogs
|
|
141
|
-
For dashboards please check
|
|
142
|
-
For CID deployment tool, including
|
|
141
|
+
For dashboards please check change log [here](changes/)
|
|
142
|
+
For CID deployment tool, including CLI and CFN please check [Releases](/releases)
|
|
143
143
|
|
|
144
144
|
## Feedback
|
|
145
145
|
Please reference to [this page](https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/feedback-support.html)
|
|
@@ -156,4 +156,4 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform
|
|
|
156
156
|
This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file.
|
|
157
157
|
|
|
158
158
|
## Notices
|
|
159
|
-
Dashboards and their content: (a) are for informational purposes only, (b)
|
|
159
|
+
Dashboards and their content: (a) are for informational purposes only, (b) represent current AWS product offerings and practices, which are subject to change without notice, and (c) does not create any commitments or assurances from AWS and its affiliates, suppliers or licensors. AWS content, products or services are provided “as is” without warranties, representations, or conditions of any kind, whether express or implied. The responsibilities and liabilities of AWS to its customers are controlled by AWS agreements, and this document is not part of, nor does it modify, any agreement between AWS and its customers.
|
|
@@ -75,18 +75,6 @@ dashboards:
|
|
|
75
75
|
'item_description', 'pricing_unit', 'region', 'pricing_term', 'linked_account_id', 'savings_plan_a_r_n', 'tags_json'
|
|
76
76
|
]
|
|
77
77
|
|
|
78
|
-
TAO:
|
|
79
|
-
category: 'Advanced'
|
|
80
|
-
name: Trusted Advisor Organizational View
|
|
81
|
-
templateId: ta-organizational-view
|
|
82
|
-
dashboardId: ta-organizational-view
|
|
83
|
-
dependsOn:
|
|
84
|
-
datasets:
|
|
85
|
-
- ta-organizational-view
|
|
86
|
-
versions:
|
|
87
|
-
minTemplateVersion: 13
|
|
88
|
-
minTemplateDescription: "v1.4.0"
|
|
89
|
-
|
|
90
78
|
Trends:
|
|
91
79
|
category: 'Additional'
|
|
92
80
|
name: Trends Dashboard
|
|
@@ -167,6 +167,7 @@ def export(ctx, **kwargs):
|
|
|
167
167
|
(definition|template) A method (definition=pull json definition of Analysis OR template=create QuickSight Template)
|
|
168
168
|
--export-known-datasets
|
|
169
169
|
(no|yes) If 'yes' the export will include DataSets that are already in resources file. Default = no
|
|
170
|
+
--export-tables (no|yes). If 'yes' the export will include Tables. Default: no = only Views
|
|
170
171
|
--category TEXT The dashboards category. Default = Custom
|
|
171
172
|
--output A filename (.yaml) If provided an existing file it will be analyzed for default values and overridden
|
|
172
173
|
"""
|
|
@@ -6,7 +6,7 @@ import functools
|
|
|
6
6
|
import webbrowser
|
|
7
7
|
from string import Template
|
|
8
8
|
from typing import Dict
|
|
9
|
-
from
|
|
9
|
+
from importlib import resources
|
|
10
10
|
from importlib.metadata import entry_points
|
|
11
11
|
from functools import cached_property
|
|
12
12
|
|
|
@@ -433,12 +433,21 @@ class Cid():
|
|
|
433
433
|
except (self.athena.client.exceptions.ClientError, CidError, CidCritical) as exc:
|
|
434
434
|
raise CidCritical(f'Failed fetching parameter {prefix}{key}: {exc}.') from exc
|
|
435
435
|
if not res_list:
|
|
436
|
-
raise CidCritical(f'Failed fetching parameter {prefix}{key}, {value}. Athena returns empty results. {value.get("error")}')
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
raise CidCritical(f'Failed fetching parameter {prefix}{key}, {value}. Athena returns empty results. {value.get("error", "")}')
|
|
437
|
+
options = ['-'.join(res) for res in res_list]
|
|
438
|
+
default = value.get('default', '{default not provided}')
|
|
439
|
+
if len(options) == 1:
|
|
440
|
+
# silently taking the 1st available option
|
|
441
|
+
params[key] = options[0]
|
|
442
|
+
elif not utils.isatty():
|
|
443
|
+
if default in options:
|
|
444
|
+
# silently taking the default option if we cannot ask user
|
|
445
|
+
params[key] = default
|
|
446
|
+
else:
|
|
447
|
+
# silently taking the first option
|
|
448
|
+
params[key] = sorted(options)[0]
|
|
439
449
|
else:
|
|
440
|
-
|
|
441
|
-
default = value.get('default')
|
|
450
|
+
# asking user
|
|
442
451
|
params[key] = get_parameter(
|
|
443
452
|
param_name=prefix + key,
|
|
444
453
|
message=f"Required parameter: {key} ({value.get('description')})",
|
|
@@ -624,7 +633,16 @@ class Cid():
|
|
|
624
633
|
dataset = self.qs.describe_dataset(id=dashboard_datasets.get(dataset_name), no_cache=True)
|
|
625
634
|
|
|
626
635
|
if not isinstance(dataset, Dataset):
|
|
627
|
-
# Second chance: try to find the dataset with the id
|
|
636
|
+
# Second chance: try to find the dataset with the id from resources
|
|
637
|
+
try:
|
|
638
|
+
_ds_id = self.resources['datasets'].get(dataset_name, {}).get('data', {}).get('DataSetId')
|
|
639
|
+
if _ds_id:
|
|
640
|
+
dataset = self.qs.describe_dataset(id=_ds_id, no_cache=True)
|
|
641
|
+
except Exception as exc:
|
|
642
|
+
logger.debug(f'Failed to describe_dataset {dataset_name} {exc}')
|
|
643
|
+
|
|
644
|
+
if not isinstance(dataset, Dataset):
|
|
645
|
+
# Third chance: try to find the dataset with the id that is the name
|
|
628
646
|
try:
|
|
629
647
|
dataset = self.qs.describe_dataset(id=dataset_name, no_cache=True)
|
|
630
648
|
except Exception as exc:
|
|
@@ -662,14 +680,18 @@ class Cid():
|
|
|
662
680
|
|
|
663
681
|
if not matching_datasets:
|
|
664
682
|
reco = ''
|
|
665
|
-
|
|
666
|
-
if
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
683
|
+
new_datasets = self.create_datasets([dataset_name], known_datasets=dashboard_datasets, recursive=recursive, update=update)
|
|
684
|
+
if not new_datasets:
|
|
685
|
+
logger.warning(f'Dataset {dataset_name} is not found.')
|
|
686
|
+
if utils.exec_env()['shell'] == 'lambda':
|
|
687
|
+
# We are in lambda
|
|
688
|
+
reco = 'You can try deleting existing dataset and re-run.'
|
|
689
|
+
else:
|
|
690
|
+
# We are in command line mode
|
|
691
|
+
reco = 'Please retry with --update "yes" --force --recursive flags.'
|
|
692
|
+
raise CidCritical(f'Failed to find a Dataset "{dataset_name}" with required fields. ' + reco)
|
|
693
|
+
_ds_id = self.resources['datasets'].get(dataset_name, {}).get('data', {}).get('DataSetId')
|
|
694
|
+
dashboard_definition['datasets'][dataset_name] = f'arn:{self.base.partition}:quicksight:{self.base.region}:{self.base.account_id}:dataset/{_ds_id}'
|
|
673
695
|
elif len(matching_datasets) >= 1:
|
|
674
696
|
if len(matching_datasets) > 1:
|
|
675
697
|
# FIXME: propose a choice?
|
|
@@ -1018,10 +1040,9 @@ class Cid():
|
|
|
1018
1040
|
param_name='folder-name',
|
|
1019
1041
|
message='Please enter the folder name to create'
|
|
1020
1042
|
)
|
|
1021
|
-
folder_permissions_tpl = Template(
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
).decode('utf-8'))
|
|
1043
|
+
folder_permissions_tpl = Template(
|
|
1044
|
+
(resources.files('cid.builtin.core') / 'data/permissions/folder_permissions.json').read_text()
|
|
1045
|
+
)
|
|
1025
1046
|
columns_tpl = {
|
|
1026
1047
|
'PrincipalArn': self.qs.get_principal_arn()
|
|
1027
1048
|
}
|
|
@@ -1057,10 +1078,9 @@ class Cid():
|
|
|
1057
1078
|
columns_tpl = {
|
|
1058
1079
|
'PrincipalArn': principal_arn
|
|
1059
1080
|
}
|
|
1060
|
-
dashboard_permissions_tpl = Template(
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
).decode('utf-8'))
|
|
1081
|
+
dashboard_permissions_tpl = Template(
|
|
1082
|
+
(resources.files('cid.builtin.core') / template_filename).read_text()
|
|
1083
|
+
)
|
|
1064
1084
|
dashboard_permissions = json.loads(dashboard_permissions_tpl.safe_substitute(columns_tpl))
|
|
1065
1085
|
dashboard_params = {
|
|
1066
1086
|
"GrantPermissions": [
|
|
@@ -1085,10 +1105,9 @@ class Cid():
|
|
|
1085
1105
|
if share_method == 'account':
|
|
1086
1106
|
logger.info(f'Sharing datasets/datasources with an account is not supported, skipping')
|
|
1087
1107
|
else:
|
|
1088
|
-
data_set_permissions_tpl = Template(
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
).decode('utf-8'))
|
|
1108
|
+
data_set_permissions_tpl = Template(
|
|
1109
|
+
(resources.files('cid.builtin.core') / 'data/permissions/data_set_permissions.json').read_text()
|
|
1110
|
+
)
|
|
1092
1111
|
data_set_permissions = json.loads(data_set_permissions_tpl.safe_substitute(columns_tpl))
|
|
1093
1112
|
|
|
1094
1113
|
_datasources: Dict[str, Datasource] = {}
|
|
@@ -1103,10 +1122,9 @@ class Cid():
|
|
|
1103
1122
|
if not _datasources.get(_datasource.id):
|
|
1104
1123
|
_datasources.update({_datasource.id: _datasource})
|
|
1105
1124
|
|
|
1106
|
-
data_source_permissions_tpl = Template(
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
).decode('utf-8'))
|
|
1125
|
+
data_source_permissions_tpl = Template(
|
|
1126
|
+
(resources.files('cid.builtin.core') / f'data/permissions/data_source_permissions.json').read_text()
|
|
1127
|
+
)
|
|
1110
1128
|
data_source_permissions = json.loads(data_source_permissions_tpl.safe_substitute(columns_tpl))
|
|
1111
1129
|
for k, v in _datasources.items():
|
|
1112
1130
|
logger.info(f'Sharing data source "{v.name}" ({k})')
|
|
@@ -144,10 +144,10 @@ def export_analysis(qs, athena, glue):
|
|
|
144
144
|
|
|
145
145
|
cid_print(f' Found DataSet <BOLD>{dataset_name}<END>.')
|
|
146
146
|
if dataset_name in athena._resources.get('datasets'):
|
|
147
|
-
resources_datasets.append(dataset_name)
|
|
148
|
-
if not get_parameters().get('export-known-datasets'):
|
|
147
|
+
resources_datasets.append(dataset_name) # used later in dependencies
|
|
148
|
+
if not get_parameters().get('export-known-datasets') == 'yes':
|
|
149
149
|
cid_print(f' DataSet <BOLD>{dataset_name}<END> is in resources. Skipping.')
|
|
150
|
-
continue
|
|
150
|
+
continue # prevent export dataset
|
|
151
151
|
|
|
152
152
|
dataset_data = {
|
|
153
153
|
"DataSetId": dataset.raw['DataSetId'],
|
|
@@ -284,7 +284,13 @@ def export_analysis(qs, athena, glue):
|
|
|
284
284
|
if key in cur_tables or cur_helper.table_is_cur(name=key):
|
|
285
285
|
logger.debug(f'Skipping {key} views - it is a CUR')
|
|
286
286
|
continue
|
|
287
|
+
if key in ['account_map']:
|
|
288
|
+
cid_print(f'{key} is a special data. Processed separately. Skipping.')
|
|
289
|
+
continue
|
|
287
290
|
if isinstance(view_data.get('data'), str):
|
|
291
|
+
if 'CREATE EXTERNAL TABLE' in view_data.get('data') and not get_parameters().get('export-tables') == 'yes':
|
|
292
|
+
cid_print(f'{key} is a Glue table. Skipping. To export table definitions use `--export-tables yes`')
|
|
293
|
+
continue
|
|
288
294
|
#check if there is dependency on crawler
|
|
289
295
|
crawler_names = re.findall(r"UPDATED_BY_CRAWLER\W+?'(.+?)''", view_data.get('data'))
|
|
290
296
|
if crawler_names:
|
|
@@ -38,7 +38,7 @@ from functools import lru_cache
|
|
|
38
38
|
|
|
39
39
|
from string import Template
|
|
40
40
|
from boto3.session import Session
|
|
41
|
-
from
|
|
41
|
+
from importlib import resources
|
|
42
42
|
|
|
43
43
|
from cid.base import CidBase
|
|
44
44
|
from cid.helpers import Athena, CUR
|
|
@@ -119,10 +119,7 @@ class AccountMap(CidBase):
|
|
|
119
119
|
if view_definition.get('File'):
|
|
120
120
|
view_file = view_definition.get('File')
|
|
121
121
|
template = Template(
|
|
122
|
-
|
|
123
|
-
view_definition.get('providedBy'),
|
|
124
|
-
f'data/{view_file}'
|
|
125
|
-
).decode('utf-8')
|
|
122
|
+
(resources.files(view_definition.get('providedBy')) / f'data/{view_file}').read_text()
|
|
126
123
|
)
|
|
127
124
|
elif view_definition.get('data'):
|
|
128
125
|
template = Template(str(view_definition.get('data')))
|
|
@@ -156,10 +153,9 @@ class AccountMap(CidBase):
|
|
|
156
153
|
sql_file = 'data/queries/shared/account_map_cur2.sql'
|
|
157
154
|
else:
|
|
158
155
|
sql_file = 'data/queries/shared/account_map_dummy.sql'
|
|
159
|
-
template = Template(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
).decode('utf-8'))
|
|
156
|
+
template = Template(
|
|
157
|
+
(resources.files('cid.builtin.core') / sql_file).read_text()
|
|
158
|
+
)
|
|
163
159
|
columns_tpl = {
|
|
164
160
|
'athena_view_name': name,
|
|
165
161
|
'cur_table_name': cur.table_name,
|
|
@@ -248,19 +248,24 @@ class CUR(AbstractCUR):
|
|
|
248
248
|
def find_cur(self, database: str=None, table: str=None):
|
|
249
249
|
"""Choose CUR"""
|
|
250
250
|
metadata = None
|
|
251
|
-
cur_database = database or get_parameters().get('cur-database')
|
|
252
|
-
if table
|
|
253
|
-
|
|
251
|
+
cur_database = database or get_parameters().get('cur-database') or self.athena.DatabaseName
|
|
252
|
+
if not table and get_parameters().get('cur-table-name') and ',' not in str(get_parameters().get('cur-table-name')):
|
|
253
|
+
table = get_parameters().get('cur-table-name')
|
|
254
|
+
if table: # Table sepcified explicity, so no need to scan. Just need to make sure the table exists
|
|
254
255
|
try:
|
|
255
|
-
metadata = self.athena.get_table_metadata(
|
|
256
|
+
metadata = self.athena.get_table_metadata(table, cur_database)
|
|
256
257
|
except self.athena.client.exceptions.MetadataException as exc:
|
|
257
|
-
|
|
258
|
+
logger.debug(f'Provided cur-table-name "{table}" in database "{cur_database}" is not found. ({exc})')
|
|
259
|
+
raise CidCritical(f'Provided cur-table-name "{table}" in database "{cur_database}" is not found. Please make sure the table exists. This could also indicate a LakeFormation permission issue, see our FAQ for help.')
|
|
258
260
|
res, message = self.table_is_cur(table=metadata, return_reason=True)
|
|
259
261
|
if not res:
|
|
260
|
-
raise CidCritical(f'
|
|
261
|
-
return cur_database
|
|
262
|
+
raise CidCritical(f'Provided cur-table-name "{table}" in database "{cur_database}" is not cur. {message}')
|
|
263
|
+
return cur_database, metadata
|
|
262
264
|
|
|
263
265
|
all_cur_tables = []
|
|
266
|
+
filter_names = None
|
|
267
|
+
if ',' in str(get_parameters().get('cur-table-name')):
|
|
268
|
+
filter_names = get_parameters().get('cur-table-name').split(',')
|
|
264
269
|
if cur_database:
|
|
265
270
|
cur_databases = [cur_database]
|
|
266
271
|
else:
|
|
@@ -271,7 +276,12 @@ class CUR(AbstractCUR):
|
|
|
271
276
|
columns=self.cur_minimal_required_columns,
|
|
272
277
|
database_name=database,
|
|
273
278
|
)
|
|
274
|
-
all_cur_tables += [
|
|
279
|
+
all_cur_tables += [
|
|
280
|
+
(database, table['Name'])
|
|
281
|
+
for table in tables
|
|
282
|
+
if (not filter_names or table['Name'] in filter_names)
|
|
283
|
+
and table['Name'] not in ('cur2_proxy', 'cur2_view')
|
|
284
|
+
]
|
|
275
285
|
except self.athena.client.exceptions.ClientError as exc:
|
|
276
286
|
if 'AccessDenied' in str(exc):
|
|
277
287
|
logger.info(f'Cannot read from athena database {database}')
|
|
@@ -279,10 +289,10 @@ class CUR(AbstractCUR):
|
|
|
279
289
|
raise
|
|
280
290
|
|
|
281
291
|
if not all_cur_tables:
|
|
282
|
-
# FIXME : distinguish a case where we have NONE tables in any database. This might be because
|
|
292
|
+
# FIXME : distinguish a case where we have NONE tables in any database. This might be because lake formation
|
|
283
293
|
raise CidCritical(
|
|
284
|
-
f'CUR table not found in {self.athena.region}. We need a least one table with these columns: {self.cur_minimal_required_columns}.'
|
|
285
|
-
' Please make sure you
|
|
294
|
+
f'CUR table not found in {self.athena.region}. We need a least one Glue table with these columns: {self.cur_minimal_required_columns}.'
|
|
295
|
+
' Please make sure you have CUR, preferably with CID CloudFormation stack.'
|
|
286
296
|
' Also if you have AWS Lake Formation, the user running the tool might need additional permissions'
|
|
287
297
|
)
|
|
288
298
|
databases = set([database for database, _ in all_cur_tables])
|
|
@@ -290,7 +300,7 @@ class CUR(AbstractCUR):
|
|
|
290
300
|
choices = dict(sorted([(f'{database}.{tab}', (database, tab)) for database, tab in all_cur_tables], reverse=True))
|
|
291
301
|
else:
|
|
292
302
|
choices = dict(sorted([(f'{tab}', (database, tab)) for database, tab in all_cur_tables], reverse=True))
|
|
293
|
-
if not choices:
|
|
303
|
+
if not choices and not get_parameters().get('no-cur-creation'):
|
|
294
304
|
choices['<CREATE CUR TABLE AND CRAWLER>'] = '<CREATE CUR TABLE AND CRAWLER>'
|
|
295
305
|
answer = get_parameter(
|
|
296
306
|
param_name='cur-table-name-and-db',
|
|
@@ -303,7 +313,7 @@ class CUR(AbstractCUR):
|
|
|
303
313
|
database, table = answer.split('.')
|
|
304
314
|
else:
|
|
305
315
|
database, table = answer
|
|
306
|
-
set_parameters({'cur-table-name': table,'cur-database': database
|
|
316
|
+
set_parameters({'cur-table-name': table,'cur-database': database})
|
|
307
317
|
return database, self.athena.get_table_metadata(table, database_name=database)
|
|
308
318
|
|
|
309
319
|
def ensure_column(self, column: str, column_type: str=None):
|
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
from string import Template
|
|
8
8
|
from typing import Dict, List, Union
|
|
9
|
-
from
|
|
9
|
+
from importlib import resources
|
|
10
10
|
|
|
11
11
|
from tqdm import tqdm
|
|
12
12
|
|
|
@@ -352,10 +352,9 @@ class QuickSight(CidBase):
|
|
|
352
352
|
columns_tpl = {
|
|
353
353
|
'PrincipalArn': self.get_principal_arn()
|
|
354
354
|
}
|
|
355
|
-
data_source_permissions_tpl = Template(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
).decode('utf-8'))
|
|
355
|
+
data_source_permissions_tpl = Template(
|
|
356
|
+
(resources.files('cid.builtin.core') / 'data/permissions/data_source_permissions.json').read_text()
|
|
357
|
+
)
|
|
359
358
|
data_source_permissions = json.loads(data_source_permissions_tpl.safe_substitute(columns_tpl))
|
|
360
359
|
datasource_name = datasource_id or "CID Athena"
|
|
361
360
|
datasource_id = datasource_id or str(uuid4())
|
|
@@ -1050,10 +1049,9 @@ class QuickSight(CidBase):
|
|
|
1050
1049
|
columns_tpl = {
|
|
1051
1050
|
'PrincipalArn': self.get_principal_arn()
|
|
1052
1051
|
}
|
|
1053
|
-
data_set_permissions_tpl = Template(
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
).decode('utf-8'))
|
|
1052
|
+
data_set_permissions_tpl = Template(
|
|
1053
|
+
(resources.files('cid.builtin.core') / 'data/permissions/data_set_permissions.json').read_text()
|
|
1054
|
+
)
|
|
1057
1055
|
data_set_permissions = json.loads(data_set_permissions_tpl.safe_substitute(columns_tpl))
|
|
1058
1056
|
definition.update({
|
|
1059
1057
|
'AwsAccountId': self.account_id,
|
|
@@ -1265,10 +1263,9 @@ class QuickSight(CidBase):
|
|
|
1265
1263
|
|
|
1266
1264
|
create_parameters = self._build_params_for_create_update_dash(definition)
|
|
1267
1265
|
|
|
1268
|
-
dashboard_permissions_tpl = Template(
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
).decode('utf-8'))
|
|
1266
|
+
dashboard_permissions_tpl = Template(
|
|
1267
|
+
(resources.files('cid.builtin.core') / 'data/permissions/dashboard_permissions.json').read_text()
|
|
1268
|
+
)
|
|
1272
1269
|
columns_tpl = {
|
|
1273
1270
|
'PrincipalArn': self.get_principal_arn()
|
|
1274
1271
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Implements generic Plugin class to load plugins
|
|
2
|
+
|
|
3
|
+
import json, yaml
|
|
4
|
+
from importlib import resources
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class Plugin():
|
|
10
|
+
|
|
11
|
+
def __init__(self, name):
|
|
12
|
+
logger.debug(f'Initializing plugin {name}')
|
|
13
|
+
self.resources = {}
|
|
14
|
+
self.name = name
|
|
15
|
+
data_directory = 'data'
|
|
16
|
+
data_path = resources.files(self.name) / data_directory
|
|
17
|
+
if data_path.is_dir():
|
|
18
|
+
for resource_file in data_path.iterdir():
|
|
19
|
+
if resource_file.is_file():
|
|
20
|
+
logger.debug(f'Located data file: {resource_file.name}')
|
|
21
|
+
ext = resource_file.name.rsplit('.', -1)[-1].lower()
|
|
22
|
+
content = None
|
|
23
|
+
if ext == 'json':
|
|
24
|
+
content = json.loads(resource_file.read_text())
|
|
25
|
+
logger.debug(f'Loaded {resource_file.name} as JSON')
|
|
26
|
+
elif ext in ['yaml', 'yml']:
|
|
27
|
+
content = yaml.load(resource_file.read_text(), Loader=yaml.SafeLoader)
|
|
28
|
+
logger.debug(f'Loaded {resource_file.name} as YAML')
|
|
29
|
+
if content is None:
|
|
30
|
+
logger.info(f'Unsupported file type: {resource_file.name}')
|
|
31
|
+
continue
|
|
32
|
+
# If plugin has resources defined in different files,
|
|
33
|
+
# they will be merged into one dict
|
|
34
|
+
resource_kind = resource_file.name.rsplit('.', -1)[0]
|
|
35
|
+
supported_resource_kinds = ['dashboards', 'views', 'datasets']
|
|
36
|
+
if resource_kind in supported_resource_kinds:
|
|
37
|
+
self.resources[resource_kind] = content
|
|
38
|
+
logger.info(f'Loaded {resource_kind} from {resource_file.name}')
|
|
39
|
+
# If plugin has resources defined in one file,
|
|
40
|
+
# simply add it to resources dict
|
|
41
|
+
else:
|
|
42
|
+
self.resources.update(content)
|
|
43
|
+
# Add plugin name to every resource
|
|
44
|
+
for v in self.resources.values():
|
|
45
|
+
for item in v.values():
|
|
46
|
+
if item is not None:
|
|
47
|
+
item['providedBy'] = self.name
|
|
48
|
+
item.update({'source': str(resource_file)})
|
|
49
|
+
logger.debug(f'Plugin {self.name} initialized')
|
|
50
|
+
|
|
51
|
+
def provides(self) -> dict:
|
|
52
|
+
logger.debug(f'Provides: {self.resources}')
|
|
53
|
+
return self.resources
|
|
54
|
+
|
|
55
|
+
def get_resource(self, resource_name) -> str:
|
|
56
|
+
_resource_path = resources.files(self.name) / f'data/{resource_name}'
|
|
57
|
+
if _resource_path.is_file():
|
|
58
|
+
logger.info(f'Resource {resource_name} found')
|
|
59
|
+
_content = _resource_path.read_text()
|
|
60
|
+
logger.debug(f'Resource {resource_name} content: {_content}')
|
|
61
|
+
return _content
|
|
62
|
+
return None
|