ic-code 1.0.0__tar.gz → 1.0.2__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.
- {ic_code-1.0.0/src/ic_code.egg-info → ic_code-1.0.2}/PKG-INFO +9 -5
- {ic_code-1.0.0 → ic_code-1.0.2}/README.md +8 -4
- ic_code-1.0.2/aws/cloudfront/__init__.py +16 -0
- ic_code-1.0.2/aws/cloudfront/info.py +205 -0
- ic_code-1.0.2/aws/profile/__init__.py +14 -0
- ic_code-1.0.2/aws/profile/info.py +311 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/cf/dns/list_info.py +1 -1
- ic_code-1.0.2/docs/config_migration.md +169 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/configuration.md +3 -1
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/migration_guide.md +4 -2
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/security.md +5 -4
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/user_guide.md +9 -3
- ic_code-1.0.2/oci_module/compartment/__init__.py +16 -0
- ic_code-1.0.2/oci_module/compartment/info.py +195 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/pyproject.toml +1 -1
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/validate_config.py +13 -3
- {ic_code-1.0.0 → ic_code-1.0.2}/setup.py +82 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/cli.py +365 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/commands/config.py +243 -2
- ic_code-1.0.2/src/ic/config/installer.py +360 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/manager.py +228 -6
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/schema.py +1 -1
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/secrets.py +16 -9
- {ic_code-1.0.0 → ic_code-1.0.2/src/ic_code.egg-info}/PKG-INFO +9 -5
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic_code.egg-info/SOURCES.txt +8 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/CHANGELOG.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/LICENSE +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/MANIFEST.in +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/SECURITY.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/codepipeline/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/codepipeline/build.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/codepipeline/deploy.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ec2/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ec2/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ec2/list_tags.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ec2/tag_check.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ecs/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ecs/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ecs/service.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/ecs/task.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/eks/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/eks/addons.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/eks/fargate.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/eks/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/eks/nodes.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/eks/pods.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/eks/update_config.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/fargate/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/fargate/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/lb/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/lb/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/lb/list_tags.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/lb/tag_check.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/msk/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/msk/broker.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/msk/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/nat/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/nat/list_tags.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/nat/tag_check.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/rds/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/rds/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/rds/list_tags.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/rds/tag_check.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/s3/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/s3/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/s3/list_tags.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/s3/tag_check.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/sg/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/sg/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/vpc/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/vpc/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/vpc/list_tags.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/vpc/tag_check.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/vpn/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/aws/vpn/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/aci/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/aks/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/lb/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/nsg/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/storage/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/vm/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/azure_module/vnet/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/cf/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/cf/dns/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/azure_utils.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/gather_env.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/gcp_config_validator.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/gcp_monitoring.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/gcp_utils.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/log.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/slack.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/common/utils.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/config/default.yaml +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/config/secrets.yaml.example +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/PYPI_DEPLOYMENT.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/README.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/aws/AWS_IMPLEMENTATION_SUMMARY.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/aws/README.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/aws/aws_cli_prd.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/deployment.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/final_validation_report.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/gcp/GCP_CONFIGURATION_GUIDE.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/gcp/GCP_SECURITY_BEST_PRACTICES.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/installation.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/mcp_integration.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/migration.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/migration_history.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/docs/troubleshooting.md +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/billing/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/billing/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/compute/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/compute/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/firewall/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/firewall/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/functions/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/functions/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/gke/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/gke/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/lb/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/lb/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/run/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/run/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/sql/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/sql/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/storage/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/storage/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/vpc/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/gcp/vpc/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/mcp/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/mcp/gcp_connector.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/common/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/common/utils.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/cost/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/cost/credit.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/cost/usage.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/info/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/info/oci_info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/lb/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/lb/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/nsg/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/nsg/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/obj/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/obj/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/policy/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/policy/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/policy/search.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/vcn/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/vcn/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/vm/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/vm/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/volume/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/oci_module/volume/info.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/requirements.txt +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/bump-version.sh +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/cf_edge_nsg_make/cf_edge_nsg_make.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/deploy.sh +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/final_validation_and_documentation.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/migration_execution_and_validation.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/oracle-policy/user_policy.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/performance_optimization.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/setup_venv.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/scripts/unset_env.sh +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/setup.cfg +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/commands/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/compat/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/compat/cli.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/compat/common.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/cleanup.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/docs_organizer.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/external.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/migration.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/config/security.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/core/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/core/logging.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/core/mcp_manager.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/core/session.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic/core/silence_logging.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic_code.egg-info/dependency_links.txt +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic_code.egg-info/entry_points.txt +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic_code.egg-info/requires.txt +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/src/ic_code.egg-info/top_level.txt +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/ssh/__init__.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/ssh/auto_ssh.py +0 -0
- {ic_code-1.0.0 → ic_code-1.0.2}/ssh/server_info.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ic-code
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Infrastructure CLI tool for managing AWS, CloudFlare, SSH, and more
|
|
5
5
|
Home-page: https://github.com/dgr009/ic
|
|
6
6
|
Author: SangYun Kim
|
|
@@ -99,14 +99,18 @@ IC CLI uses a modern YAML-based configuration system with separate files for def
|
|
|
99
99
|
#### 1. Create Configuration Files
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
# Copy example files
|
|
102
|
+
# Copy example files (new preferred location)
|
|
103
|
+
mkdir -p .ic/config
|
|
104
|
+
cp .ic/config/secrets.yaml.example .ic/config/secrets.yaml
|
|
105
|
+
|
|
106
|
+
# Or use legacy location for backward compatibility
|
|
103
107
|
cp config/secrets.yaml.example config/secrets.yaml
|
|
104
108
|
|
|
105
109
|
# Edit with your actual values
|
|
106
|
-
vim config/secrets.yaml
|
|
110
|
+
vim .ic/config/secrets.yaml
|
|
107
111
|
```
|
|
108
112
|
|
|
109
|
-
#### 2. Configure Secrets (config/secrets.yaml)
|
|
113
|
+
#### 2. Configure Secrets (~/.ic/config/secrets.yaml)
|
|
110
114
|
|
|
111
115
|
```yaml
|
|
112
116
|
# AWS Configuration
|
|
@@ -139,7 +143,7 @@ oci:
|
|
|
139
143
|
profile: "DEFAULT"
|
|
140
144
|
```
|
|
141
145
|
|
|
142
|
-
#### 3. Default Settings (config/default.yaml)
|
|
146
|
+
#### 3. Default Settings (.ic/config/default.yaml)
|
|
143
147
|
|
|
144
148
|
The default configuration is already provided and includes:
|
|
145
149
|
- AWS regions
|
|
@@ -50,14 +50,18 @@ IC CLI uses a modern YAML-based configuration system with separate files for def
|
|
|
50
50
|
#### 1. Create Configuration Files
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
# Copy example files
|
|
53
|
+
# Copy example files (new preferred location)
|
|
54
|
+
mkdir -p .ic/config
|
|
55
|
+
cp .ic/config/secrets.yaml.example .ic/config/secrets.yaml
|
|
56
|
+
|
|
57
|
+
# Or use legacy location for backward compatibility
|
|
54
58
|
cp config/secrets.yaml.example config/secrets.yaml
|
|
55
59
|
|
|
56
60
|
# Edit with your actual values
|
|
57
|
-
vim config/secrets.yaml
|
|
61
|
+
vim .ic/config/secrets.yaml
|
|
58
62
|
```
|
|
59
63
|
|
|
60
|
-
#### 2. Configure Secrets (config/secrets.yaml)
|
|
64
|
+
#### 2. Configure Secrets (~/.ic/config/secrets.yaml)
|
|
61
65
|
|
|
62
66
|
```yaml
|
|
63
67
|
# AWS Configuration
|
|
@@ -90,7 +94,7 @@ oci:
|
|
|
90
94
|
profile: "DEFAULT"
|
|
91
95
|
```
|
|
92
96
|
|
|
93
|
-
#### 3. Default Settings (config/default.yaml)
|
|
97
|
+
#### 3. Default Settings (.ic/config/default.yaml)
|
|
94
98
|
|
|
95
99
|
The default configuration is already provided and includes:
|
|
96
100
|
- AWS regions
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS CloudFront module for distribution information and management.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to collect and display CloudFront distribution
|
|
5
|
+
information including origins, domains, and price classes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__author__ = "IC Development Team"
|
|
10
|
+
|
|
11
|
+
from .info import CloudFrontCollector, CloudFrontRenderer
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CloudFrontCollector",
|
|
15
|
+
"CloudFrontRenderer"
|
|
16
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS CloudFront distribution information collector and renderer.
|
|
3
|
+
|
|
4
|
+
This module provides classes to collect CloudFront distribution details
|
|
5
|
+
and render them in a user-friendly table format.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import boto3
|
|
9
|
+
from typing import Dict, List, Any, Optional
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
|
13
|
+
|
|
14
|
+
import boto3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CloudFrontCollector:
|
|
18
|
+
"""Collects CloudFront distribution information from AWS."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.console = Console()
|
|
22
|
+
|
|
23
|
+
def collect_distributions(self, account_profiles: Dict[str, str]) -> List[Dict[str, str]]:
|
|
24
|
+
"""
|
|
25
|
+
Collect CloudFront distribution information from multiple accounts.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
account_profiles: Dictionary mapping account names to AWS profile names
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of distribution information dictionaries
|
|
32
|
+
"""
|
|
33
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
|
|
34
|
+
import time
|
|
35
|
+
|
|
36
|
+
distributions = []
|
|
37
|
+
|
|
38
|
+
with Progress(
|
|
39
|
+
SpinnerColumn(),
|
|
40
|
+
TextColumn("[progress.description]{task.description}"),
|
|
41
|
+
BarColumn(),
|
|
42
|
+
TaskProgressColumn(),
|
|
43
|
+
console=self.console
|
|
44
|
+
) as progress:
|
|
45
|
+
|
|
46
|
+
# Create main task
|
|
47
|
+
main_task = progress.add_task(
|
|
48
|
+
f"Collecting CloudFront distributions from {len(account_profiles)} account(s)...",
|
|
49
|
+
total=len(account_profiles)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
for account_name, profile_name in account_profiles.items():
|
|
53
|
+
# Update task description
|
|
54
|
+
progress.update(main_task, description=f"Processing account: {account_name}")
|
|
55
|
+
|
|
56
|
+
start_time = time.time()
|
|
57
|
+
try:
|
|
58
|
+
session = boto3.Session(profile_name=profile_name)
|
|
59
|
+
account_distributions = self._get_account_distributions(session, account_name)
|
|
60
|
+
distributions.extend(account_distributions)
|
|
61
|
+
|
|
62
|
+
# Calculate processing time
|
|
63
|
+
processing_time = time.time() - start_time
|
|
64
|
+
progress.console.print(
|
|
65
|
+
f"✅ {account_name}: Found {len(account_distributions)} distributions "
|
|
66
|
+
f"({processing_time:.2f}s)"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
progress.console.print(f"❌ Failed to collect CloudFront data for {account_name}: {e}")
|
|
71
|
+
|
|
72
|
+
# Advance progress
|
|
73
|
+
progress.advance(main_task)
|
|
74
|
+
|
|
75
|
+
return distributions
|
|
76
|
+
|
|
77
|
+
def _get_account_distributions(self, session: boto3.Session, account_name: str) -> List[Dict[str, str]]:
|
|
78
|
+
"""
|
|
79
|
+
Get CloudFront distributions for a specific account.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
session: AWS session for the account
|
|
83
|
+
account_name: Name of the AWS account
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of distribution information dictionaries
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
cloudfront_client = session.client('cloudfront')
|
|
90
|
+
|
|
91
|
+
# List all distributions
|
|
92
|
+
response = cloudfront_client.list_distributions()
|
|
93
|
+
distributions = []
|
|
94
|
+
|
|
95
|
+
if 'DistributionList' in response and 'Items' in response['DistributionList']:
|
|
96
|
+
for distribution in response['DistributionList']['Items']:
|
|
97
|
+
dist_info = self.get_distribution_details(distribution, account_name)
|
|
98
|
+
distributions.append(dist_info)
|
|
99
|
+
|
|
100
|
+
return distributions
|
|
101
|
+
|
|
102
|
+
except ClientError as e:
|
|
103
|
+
error_code = e.response['Error']['Code']
|
|
104
|
+
if error_code == 'AccessDenied':
|
|
105
|
+
self.console.print(f"⚠️ Access denied for CloudFront in {account_name}")
|
|
106
|
+
else:
|
|
107
|
+
self.console.print(f"❌ CloudFront API error in {account_name}: {e}")
|
|
108
|
+
return []
|
|
109
|
+
except NoCredentialsError:
|
|
110
|
+
self.console.print(f"❌ No credentials available for {account_name}")
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
def get_distribution_details(self, distribution: Dict[str, Any], account_name: str) -> Dict[str, str]:
|
|
114
|
+
"""
|
|
115
|
+
Extract and format distribution details.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
distribution: CloudFront distribution data from AWS API
|
|
119
|
+
account_name: Name of the AWS account
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dictionary with formatted distribution information
|
|
123
|
+
"""
|
|
124
|
+
return {
|
|
125
|
+
'account': account_name,
|
|
126
|
+
'ID': distribution.get('Id', 'N/A'),
|
|
127
|
+
'Name': distribution.get('Comment', 'N/A') or 'N/A',
|
|
128
|
+
'원본(Origin)': self.get_primary_origin(distribution.get('Origins', {}).get('Items', [])),
|
|
129
|
+
'도메인(Domain)': distribution.get('DomainName', 'N/A'),
|
|
130
|
+
'Class': self.format_price_class(distribution.get('PriceClass', 'PriceClass_All'))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
def get_primary_origin(self, origins: List[Dict[str, Any]]) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Get the primary origin or indicate multiple origins.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
origins: List of origin configurations
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Primary origin domain or "Multiple" indicator
|
|
142
|
+
"""
|
|
143
|
+
if not origins:
|
|
144
|
+
return 'N/A'
|
|
145
|
+
elif len(origins) == 1:
|
|
146
|
+
return origins[0].get('DomainName', 'N/A')
|
|
147
|
+
else:
|
|
148
|
+
return f'Multiple ({len(origins)})'
|
|
149
|
+
|
|
150
|
+
def format_price_class(self, price_class: str) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Convert technical price class names to human-readable format.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
price_class: Technical price class name from AWS
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Human-readable price class description
|
|
159
|
+
"""
|
|
160
|
+
price_class_mapping = {
|
|
161
|
+
'PriceClass_All': 'All Edge Locations',
|
|
162
|
+
'PriceClass_200': 'North America and Europe Only',
|
|
163
|
+
'PriceClass_100': 'North America Only'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return price_class_mapping.get(price_class, price_class)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class CloudFrontRenderer:
|
|
170
|
+
"""Renders CloudFront distribution information in table format."""
|
|
171
|
+
|
|
172
|
+
def __init__(self):
|
|
173
|
+
self.console = Console()
|
|
174
|
+
|
|
175
|
+
def render_distributions(self, distributions: List[Dict[str, str]]) -> None:
|
|
176
|
+
"""
|
|
177
|
+
Render CloudFront distributions in a Rich table.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
distributions: List of distribution information dictionaries
|
|
181
|
+
"""
|
|
182
|
+
if not distributions:
|
|
183
|
+
self.console.print("📋 No CloudFront distributions found.")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
table = Table(title="CloudFront Distributions")
|
|
187
|
+
table.add_column("Account", style="cyan", no_wrap=True)
|
|
188
|
+
table.add_column("ID", style="green", no_wrap=True)
|
|
189
|
+
table.add_column("Name", style="yellow")
|
|
190
|
+
table.add_column("원본(Origin)", style="blue")
|
|
191
|
+
table.add_column("도메인(Domain)", style="magenta")
|
|
192
|
+
table.add_column("Class", style="red")
|
|
193
|
+
|
|
194
|
+
for dist in distributions:
|
|
195
|
+
table.add_row(
|
|
196
|
+
dist['account'],
|
|
197
|
+
dist['ID'],
|
|
198
|
+
dist['Name'],
|
|
199
|
+
dist['원본(Origin)'],
|
|
200
|
+
dist['도메인(Domain)'],
|
|
201
|
+
dist['Class']
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self.console.print(table)
|
|
205
|
+
self.console.print(f"\n📊 Total distributions: {len(distributions)}")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Profile management module.
|
|
3
|
+
|
|
4
|
+
This module provides functionality for parsing and displaying AWS profile information
|
|
5
|
+
from ~/.aws/config and ~/.aws/credentials files.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .info import AWSProfileParser, ProfileInfoCollector, ProfileTableRenderer
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'AWSProfileParser',
|
|
12
|
+
'ProfileInfoCollector',
|
|
13
|
+
'ProfileTableRenderer'
|
|
14
|
+
]
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS profile information parser and renderer.
|
|
3
|
+
|
|
4
|
+
This module provides classes to parse AWS configuration and credentials files
|
|
5
|
+
and render profile information in a user-friendly table format.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import configparser
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Any, Optional
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AWSProfileParser:
|
|
18
|
+
"""Parses AWS configuration and credentials files."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.console = Console()
|
|
22
|
+
self.aws_config_path = Path.home() / ".aws" / "config"
|
|
23
|
+
self.aws_credentials_path = Path.home() / ".aws" / "credentials"
|
|
24
|
+
|
|
25
|
+
def parse_config_file(self, config_path: Optional[str] = None) -> Dict[str, Dict[str, str]]:
|
|
26
|
+
"""
|
|
27
|
+
Parse AWS config file (~/.aws/config).
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
config_path: Optional path to config file
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dictionary of profile configurations
|
|
34
|
+
"""
|
|
35
|
+
if config_path:
|
|
36
|
+
config_file = Path(config_path)
|
|
37
|
+
else:
|
|
38
|
+
config_file = self.aws_config_path
|
|
39
|
+
|
|
40
|
+
if not config_file.exists():
|
|
41
|
+
self.console.print(f"⚠️ AWS config file not found: {config_file}")
|
|
42
|
+
return {}
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
config = configparser.ConfigParser()
|
|
46
|
+
config.read(config_file)
|
|
47
|
+
|
|
48
|
+
profiles = {}
|
|
49
|
+
for section_name in config.sections():
|
|
50
|
+
# Handle both 'profile name' and 'name' formats
|
|
51
|
+
if section_name.startswith('profile '):
|
|
52
|
+
profile_name = section_name[8:] # Remove 'profile ' prefix
|
|
53
|
+
else:
|
|
54
|
+
profile_name = section_name
|
|
55
|
+
|
|
56
|
+
profile_data = dict(config[section_name])
|
|
57
|
+
profiles[profile_name] = profile_data
|
|
58
|
+
|
|
59
|
+
return profiles
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
self.console.print(f"❌ Failed to parse AWS config file: {e}")
|
|
63
|
+
return {}
|
|
64
|
+
|
|
65
|
+
def parse_credentials_file(self, creds_path: Optional[str] = None) -> Dict[str, Dict[str, str]]:
|
|
66
|
+
"""
|
|
67
|
+
Parse AWS credentials file (~/.aws/credentials).
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
creds_path: Optional path to credentials file
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dictionary of profile credentials
|
|
74
|
+
"""
|
|
75
|
+
if creds_path:
|
|
76
|
+
creds_file = Path(creds_path)
|
|
77
|
+
else:
|
|
78
|
+
creds_file = self.aws_credentials_path
|
|
79
|
+
|
|
80
|
+
if not creds_file.exists():
|
|
81
|
+
self.console.print(f"⚠️ AWS credentials file not found: {creds_file}")
|
|
82
|
+
return {}
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
config = configparser.ConfigParser()
|
|
86
|
+
config.read(creds_file)
|
|
87
|
+
|
|
88
|
+
credentials = {}
|
|
89
|
+
for section_name in config.sections():
|
|
90
|
+
profile_data = dict(config[section_name])
|
|
91
|
+
credentials[section_name] = profile_data
|
|
92
|
+
|
|
93
|
+
return credentials
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self.console.print(f"❌ Failed to parse AWS credentials file: {e}")
|
|
97
|
+
return {}
|
|
98
|
+
|
|
99
|
+
def extract_account_from_role_arn(self, role_arn: str) -> Optional[str]:
|
|
100
|
+
"""
|
|
101
|
+
Extract account ID from role ARN.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
role_arn: AWS role ARN (e.g., arn:aws:iam::123456789012:role/RoleName)
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Account ID or None if not found
|
|
108
|
+
"""
|
|
109
|
+
if not role_arn:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
# Pattern: arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME
|
|
113
|
+
arn_pattern = r'arn:aws:iam::(\d{12}):role/'
|
|
114
|
+
match = re.search(arn_pattern, role_arn)
|
|
115
|
+
|
|
116
|
+
if match:
|
|
117
|
+
return match.group(1)
|
|
118
|
+
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def extract_role_name_from_arn(self, role_arn: str) -> Optional[str]:
|
|
122
|
+
"""
|
|
123
|
+
Extract role name from role ARN (last part after final slash).
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
role_arn: AWS role ARN
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Role name or None if not found
|
|
130
|
+
"""
|
|
131
|
+
if not role_arn:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
# Extract everything after the last slash
|
|
135
|
+
parts = role_arn.split('/')
|
|
136
|
+
if len(parts) > 1:
|
|
137
|
+
return parts[-1]
|
|
138
|
+
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ProfileInfoCollector:
|
|
143
|
+
"""Collects and formats AWS profile information."""
|
|
144
|
+
|
|
145
|
+
def __init__(self):
|
|
146
|
+
self.parser = AWSProfileParser()
|
|
147
|
+
self.console = Console()
|
|
148
|
+
|
|
149
|
+
def collect_profile_info(self) -> List[Dict[str, str]]:
|
|
150
|
+
"""
|
|
151
|
+
Collect comprehensive AWS profile information.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of profile information dictionaries
|
|
155
|
+
"""
|
|
156
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
157
|
+
import time
|
|
158
|
+
|
|
159
|
+
with Progress(
|
|
160
|
+
SpinnerColumn(),
|
|
161
|
+
TextColumn("[progress.description]{task.description}"),
|
|
162
|
+
console=self.console
|
|
163
|
+
) as progress:
|
|
164
|
+
|
|
165
|
+
# Parse config file
|
|
166
|
+
task = progress.add_task("Parsing AWS configuration files...", total=None)
|
|
167
|
+
start_time = time.time()
|
|
168
|
+
|
|
169
|
+
config_data = self.parser.parse_config_file()
|
|
170
|
+
progress.update(task, description="Parsing AWS credentials file...")
|
|
171
|
+
|
|
172
|
+
creds_data = self.parser.parse_credentials_file()
|
|
173
|
+
progress.update(task, description="Merging profile information...")
|
|
174
|
+
|
|
175
|
+
# Merge and format profile information
|
|
176
|
+
profiles = self.merge_config_and_credentials(config_data, creds_data)
|
|
177
|
+
|
|
178
|
+
# Calculate processing time
|
|
179
|
+
processing_time = time.time() - start_time
|
|
180
|
+
progress.update(task, description=f"Completed in {processing_time:.2f}s")
|
|
181
|
+
progress.stop()
|
|
182
|
+
|
|
183
|
+
return profiles
|
|
184
|
+
|
|
185
|
+
def merge_config_and_credentials(self, config_data: Dict[str, Dict[str, str]],
|
|
186
|
+
creds_data: Dict[str, Dict[str, str]]) -> List[Dict[str, str]]:
|
|
187
|
+
"""
|
|
188
|
+
Merge configuration and credentials data into unified profile information.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
config_data: Data from ~/.aws/config
|
|
192
|
+
creds_data: Data from ~/.aws/credentials
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
List of merged profile information
|
|
196
|
+
"""
|
|
197
|
+
profiles = []
|
|
198
|
+
|
|
199
|
+
# Get all unique profile names
|
|
200
|
+
all_profiles = set(config_data.keys()) | set(creds_data.keys())
|
|
201
|
+
|
|
202
|
+
for profile_name in sorted(all_profiles):
|
|
203
|
+
config_info = config_data.get(profile_name, {})
|
|
204
|
+
creds_info = creds_data.get(profile_name, {})
|
|
205
|
+
|
|
206
|
+
# Extract profile information
|
|
207
|
+
profile_info = {
|
|
208
|
+
'profile_name': profile_name,
|
|
209
|
+
'account_id': self._extract_account_id(config_info, creds_info),
|
|
210
|
+
'source': config_info.get('source_profile', ''),
|
|
211
|
+
'role_name': self._extract_role_name(config_info),
|
|
212
|
+
'credential': 'active' if creds_info else 'inactive',
|
|
213
|
+
'region': config_info.get('region', ''),
|
|
214
|
+
'role_arn': config_info.get('role_arn', '')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
profiles.append(profile_info)
|
|
218
|
+
|
|
219
|
+
return profiles
|
|
220
|
+
|
|
221
|
+
def _extract_account_id(self, config_info: Dict[str, str], creds_info: Dict[str, str]) -> str:
|
|
222
|
+
"""Extract account ID from various sources."""
|
|
223
|
+
# Try direct account_id first
|
|
224
|
+
if 'account_id' in config_info:
|
|
225
|
+
return config_info['account_id']
|
|
226
|
+
|
|
227
|
+
# Try extracting from role_arn
|
|
228
|
+
if 'role_arn' in config_info:
|
|
229
|
+
account_id = self.parser.extract_account_from_role_arn(config_info['role_arn'])
|
|
230
|
+
if account_id:
|
|
231
|
+
return account_id
|
|
232
|
+
|
|
233
|
+
# Try from credentials (less common)
|
|
234
|
+
if 'account_id' in creds_info:
|
|
235
|
+
return creds_info['account_id']
|
|
236
|
+
|
|
237
|
+
return ''
|
|
238
|
+
|
|
239
|
+
def _extract_role_name(self, config_info: Dict[str, str]) -> str:
|
|
240
|
+
"""Extract role name from configuration."""
|
|
241
|
+
if 'role_arn' in config_info:
|
|
242
|
+
role_name = self.parser.extract_role_name_from_arn(config_info['role_arn'])
|
|
243
|
+
if role_name:
|
|
244
|
+
return role_name
|
|
245
|
+
|
|
246
|
+
# Fallback to role_name if directly specified
|
|
247
|
+
return config_info.get('role_name', '')
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class ProfileTableRenderer:
|
|
251
|
+
"""Renders AWS profile information in table format using Rich."""
|
|
252
|
+
|
|
253
|
+
def __init__(self):
|
|
254
|
+
self.console = Console()
|
|
255
|
+
|
|
256
|
+
def render_profiles(self, profiles: List[Dict[str, str]]) -> None:
|
|
257
|
+
"""
|
|
258
|
+
Render AWS profiles in a Rich table.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
profiles: List of profile information dictionaries
|
|
262
|
+
"""
|
|
263
|
+
if not profiles:
|
|
264
|
+
self.console.print("📋 No AWS profiles found.")
|
|
265
|
+
self.console.print("\n💡 Suggestions:")
|
|
266
|
+
self.console.print(" • Check if ~/.aws/config and ~/.aws/credentials files exist")
|
|
267
|
+
self.console.print(" • Run 'aws configure' to set up your first profile")
|
|
268
|
+
self.console.print(" • Verify AWS CLI installation")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
table = Table(title="AWS Profiles")
|
|
272
|
+
table.add_column("Profile Name", style="cyan", no_wrap=True)
|
|
273
|
+
table.add_column("Account ID", style="green", no_wrap=True)
|
|
274
|
+
table.add_column("Source", style="yellow")
|
|
275
|
+
table.add_column("Role Name", style="blue")
|
|
276
|
+
table.add_column("Credential", style="magenta")
|
|
277
|
+
table.add_column("Region", style="red")
|
|
278
|
+
|
|
279
|
+
for profile in profiles:
|
|
280
|
+
# Format credential status with colors
|
|
281
|
+
credential_status = profile['credential']
|
|
282
|
+
if credential_status == 'active':
|
|
283
|
+
credential_display = f"[green]{credential_status}[/green]"
|
|
284
|
+
else:
|
|
285
|
+
credential_display = f"[red]{credential_status}[/red]"
|
|
286
|
+
|
|
287
|
+
table.add_row(
|
|
288
|
+
profile['profile_name'],
|
|
289
|
+
profile['account_id'] or '-',
|
|
290
|
+
profile['source'] or '-',
|
|
291
|
+
profile['role_name'] or '-',
|
|
292
|
+
credential_display,
|
|
293
|
+
profile['region'] or '-'
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
self.console.print(table)
|
|
297
|
+
|
|
298
|
+
# Display summary
|
|
299
|
+
active_profiles = sum(1 for p in profiles if p['credential'] == 'active')
|
|
300
|
+
self.console.print(f"\n📊 Total profiles: {len(profiles)} | Active: {active_profiles} | Inactive: {len(profiles) - active_profiles}")
|
|
301
|
+
|
|
302
|
+
# Display helpful information
|
|
303
|
+
if active_profiles == 0:
|
|
304
|
+
self.console.print("\n⚠️ No active profiles found (no credentials available)")
|
|
305
|
+
self.console.print("💡 Run 'aws configure' to set up credentials for your profiles")
|
|
306
|
+
|
|
307
|
+
# Show profiles with missing account IDs
|
|
308
|
+
missing_accounts = [p['profile_name'] for p in profiles if not p['account_id']]
|
|
309
|
+
if missing_accounts:
|
|
310
|
+
self.console.print(f"\n⚠️ Profiles without account ID: {', '.join(missing_accounts)}")
|
|
311
|
+
self.console.print("💡 Consider adding role_arn or account_id to these profiles")
|
|
@@ -162,7 +162,7 @@ def info(args):
|
|
|
162
162
|
"""
|
|
163
163
|
# 인증 정보 검사
|
|
164
164
|
if not CF_EMAIL or not CF_TOKEN:
|
|
165
|
-
log_error("CloudFlare 인증 정보가 설정되지 않았습니다. config/secrets.yaml을 확인하세요.")
|
|
165
|
+
log_error("CloudFlare 인증 정보가 설정되지 않았습니다. .ic/config/secrets.yaml 또는 config/secrets.yaml을 확인하세요.")
|
|
166
166
|
return
|
|
167
167
|
|
|
168
168
|
env_accounts = []
|