mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-21 18:58:04 +00:00
feat(oraclecloud): add automated OCI regions updater script and CI workflow (#10020)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
committed by
GitHub
parent
1f4e308374
commit
e6bea9f25a
95
.github/workflows/sdk-refresh-oci-regions.yml
vendored
Normal file
95
.github/workflows/sdk-refresh-oci-regions.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
name: 'SDK: Refresh OCI Regions'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * 1' # Every Monday at 09:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: '3.12'
|
||||
|
||||
jobs:
|
||||
refresh-oci-regions:
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
ref: 'master'
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install oci
|
||||
|
||||
- name: Update OCI regions
|
||||
env:
|
||||
OCI_CLI_USER: ${{ secrets.E2E_OCI_USER_ID }}
|
||||
OCI_CLI_FINGERPRINT: ${{ secrets.E2E_OCI_FINGERPRINT }}
|
||||
OCI_CLI_TENANCY: ${{ secrets.E2E_OCI_TENANCY_ID }}
|
||||
OCI_CLI_KEY_CONTENT: ${{ secrets.E2E_OCI_KEY_CONTENT }}
|
||||
OCI_CLI_REGION: ${{ secrets.E2E_OCI_REGION }}
|
||||
run: python util/update_oci_regions.py
|
||||
|
||||
- name: Create pull request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
|
||||
committer: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
|
||||
commit-message: 'feat(oraclecloud): update commercial regions'
|
||||
branch: 'oci-regions-update-${{ github.run_number }}'
|
||||
title: 'feat(oraclecloud): Update commercial regions'
|
||||
labels: |
|
||||
status/waiting-for-revision
|
||||
severity/low
|
||||
provider/oraclecloud
|
||||
no-changelog
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Automated update of OCI commercial regions from the official Oracle Cloud Infrastructure Identity service.
|
||||
|
||||
**Trigger:** ${{ github.event_name == 'schedule' && 'Scheduled (weekly)' || github.event_name == 'workflow_dispatch' && 'Manual' || 'Workflow update' }}
|
||||
**Run:** [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
|
||||
### Changes
|
||||
|
||||
This PR updates the `OCI_COMMERCIAL_REGIONS` dictionary in `prowler/providers/oraclecloud/config.py` with the latest regions fetched from the OCI Identity API (`list_regions()`).
|
||||
|
||||
- Government regions (`OCI_GOVERNMENT_REGIONS`) are preserved unchanged
|
||||
- Region display names are mapped from Oracle's official documentation
|
||||
|
||||
### Checklist
|
||||
|
||||
- [x] This is an automated update from OCI official sources
|
||||
- [x] Government regions (us-langley-1, us-luke-1) preserved
|
||||
- [x] No manual review of region data required
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
- name: PR creation result
|
||||
run: |
|
||||
if [[ "${{ steps.create-pr.outputs.pull-request-number }}" ]]; then
|
||||
echo "✓ Pull request #${{ steps.create-pr.outputs.pull-request-number }} created successfully"
|
||||
echo "URL: ${{ steps.create-pr.outputs.pull-request-url }}"
|
||||
else
|
||||
echo "✓ No changes detected - OCI regions are up to date"
|
||||
fi
|
||||
@@ -12,6 +12,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- CSA CCM 4.0 for the AWS provider [(#10018)](https://github.com/prowler-cloud/prowler/pull/10018)
|
||||
- CSA CCM 4.0 for the GCP provider [(#10042)](https://github.com/prowler-cloud/prowler/pull/10042)
|
||||
- CSA CCM for the Azure provider [(#10039)](https://github.com/prowler-cloud/prowler/pull/10039)
|
||||
- OCI regions updater script and CI workflow [(#10020)](https://github.com/prowler-cloud/prowler/pull/10020)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
|
||||
205
util/update_oci_regions.py
Normal file
205
util/update_oci_regions.py
Normal file
@@ -0,0 +1,205 @@
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import oci
|
||||
|
||||
# Logging config
|
||||
logging.basicConfig(
|
||||
stream=sys.stdout,
|
||||
format="%(asctime)s [File: %(filename)s:%(lineno)d] \t[Module: %(module)s]\t %(levelname)s: %(message)s",
|
||||
datefmt="%m/%d/%Y %I:%M:%S %p",
|
||||
level=logging.INFO,
|
||||
)
|
||||
|
||||
# OCI regions are fetched dynamically from the Identity API
|
||||
# No hardcoded display names - following AWS regions updater pattern
|
||||
# This ensures full automation without manual maintenance
|
||||
|
||||
|
||||
def setup_oci_client():
|
||||
"""
|
||||
Set up OCI Identity client using credentials from environment variables.
|
||||
|
||||
Returns:
|
||||
oci.identity.IdentityClient: Authenticated OCI Identity client
|
||||
|
||||
Raises:
|
||||
ValueError: If required environment variables are missing
|
||||
Exception: If authentication fails
|
||||
"""
|
||||
logging.info("Setting up OCI client authentication")
|
||||
|
||||
# Validate required environment variables
|
||||
required_vars = [
|
||||
"OCI_CLI_USER",
|
||||
"OCI_CLI_FINGERPRINT",
|
||||
"OCI_CLI_TENANCY",
|
||||
"OCI_CLI_KEY_CONTENT",
|
||||
]
|
||||
|
||||
missing_vars = [var for var in required_vars if not os.getenv(var)]
|
||||
if missing_vars:
|
||||
raise ValueError(
|
||||
f"Missing required environment variables: {', '.join(missing_vars)}"
|
||||
)
|
||||
|
||||
# Get credentials from environment
|
||||
user_ocid = os.getenv("OCI_CLI_USER")
|
||||
fingerprint = os.getenv("OCI_CLI_FINGERPRINT")
|
||||
tenancy_ocid = os.getenv("OCI_CLI_TENANCY")
|
||||
key_content_b64 = os.getenv("OCI_CLI_KEY_CONTENT")
|
||||
region = os.getenv("OCI_CLI_REGION", "us-ashburn-1")
|
||||
|
||||
# Decode base64 private key
|
||||
try:
|
||||
key_content = base64.b64decode(key_content_b64).decode("utf-8")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to decode OCI_CLI_KEY_CONTENT: {e}")
|
||||
|
||||
# Create OCI config dictionary
|
||||
config = {
|
||||
"user": user_ocid,
|
||||
"fingerprint": fingerprint,
|
||||
"tenancy": tenancy_ocid,
|
||||
"key_content": key_content,
|
||||
"region": region,
|
||||
}
|
||||
|
||||
# Initialize Identity client
|
||||
try:
|
||||
identity_client = oci.identity.IdentityClient(config)
|
||||
logging.info(f"Successfully authenticated to OCI region: {region}")
|
||||
return identity_client
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to create OCI Identity client: {e}")
|
||||
|
||||
|
||||
def fetch_oci_regions(identity_client):
|
||||
"""
|
||||
Fetch all OCI commercial regions using the Identity service API.
|
||||
|
||||
Args:
|
||||
identity_client (oci.identity.IdentityClient): Authenticated OCI client
|
||||
|
||||
Returns:
|
||||
dict: Dictionary mapping region identifiers to themselves (for consistency)
|
||||
|
||||
Raises:
|
||||
Exception: If API call fails
|
||||
"""
|
||||
logging.info("Fetching OCI commercial regions from Identity service")
|
||||
|
||||
try:
|
||||
# Call list_regions() API - returns all OC1 commercial regions
|
||||
response = identity_client.list_regions()
|
||||
regions = response.data
|
||||
|
||||
logging.info(f"Successfully fetched {len(regions)} regions from OCI API")
|
||||
|
||||
# Build region dictionary (region_id -> region_id)
|
||||
# Following AWS pattern: no display names, fully automated
|
||||
region_dict = {}
|
||||
for region in regions:
|
||||
region_id = region.name
|
||||
region_dict[region_id] = region_id
|
||||
logging.debug(f"Added region: {region_id}")
|
||||
|
||||
# Sort regions alphabetically by key
|
||||
region_dict = dict(sorted(region_dict.items()))
|
||||
|
||||
logging.info(f"Processed {len(region_dict)} commercial regions")
|
||||
return region_dict
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to fetch OCI regions: {e}")
|
||||
|
||||
|
||||
def update_config_file(regions, config_file_path):
|
||||
"""
|
||||
Update the OCI config file with new commercial regions while preserving government regions.
|
||||
|
||||
Args:
|
||||
regions (dict): Dictionary of region identifiers to region identifiers
|
||||
config_file_path (str): Path to the config.py file
|
||||
|
||||
Raises:
|
||||
Exception: If file operations fail or validation fails
|
||||
"""
|
||||
logging.info(f"Updating config file: {config_file_path}")
|
||||
|
||||
# Read current config file
|
||||
try:
|
||||
with open(config_file_path, "r") as f:
|
||||
config_content = f.read()
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to read config file: {e}")
|
||||
|
||||
# Generate new OCI_COMMERCIAL_REGIONS dictionary
|
||||
new_regions_dict = "OCI_COMMERCIAL_REGIONS = {\n"
|
||||
for region_id in regions.keys():
|
||||
new_regions_dict += f' "{region_id}": "{region_id}",\n'
|
||||
new_regions_dict += "}"
|
||||
|
||||
# Replace OCI_COMMERCIAL_REGIONS using regex
|
||||
pattern = r"OCI_COMMERCIAL_REGIONS\s*=\s*\{[^}]*\}"
|
||||
updated_content = re.sub(pattern, new_regions_dict, config_content)
|
||||
|
||||
# Validate that government regions still exist
|
||||
if "OCI_GOVERNMENT_REGIONS" not in updated_content:
|
||||
raise Exception(
|
||||
"Validation failed: OCI_GOVERNMENT_REGIONS section missing after update. Aborting to prevent data loss."
|
||||
)
|
||||
|
||||
# Verify the replacement was successful
|
||||
if updated_content == config_content:
|
||||
logging.warning("No changes detected in config file")
|
||||
return
|
||||
|
||||
# Write updated content back to file
|
||||
try:
|
||||
with open(config_file_path, "w") as f:
|
||||
f.write(updated_content)
|
||||
logging.info("Successfully updated config file")
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to write updated config file: {e}")
|
||||
|
||||
# Log summary of changes
|
||||
logging.info(f"Updated OCI_COMMERCIAL_REGIONS with {len(regions)} regions")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main execution function for OCI regions updater.
|
||||
"""
|
||||
try:
|
||||
# Setup OCI client with authentication
|
||||
identity_client = setup_oci_client()
|
||||
|
||||
# Fetch all commercial regions from OCI API
|
||||
commercial_regions = fetch_oci_regions(identity_client)
|
||||
|
||||
# Update config.py file
|
||||
config_file_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
"..",
|
||||
"prowler",
|
||||
"providers",
|
||||
"oraclecloud",
|
||||
"config.py",
|
||||
)
|
||||
|
||||
update_config_file(commercial_regions, config_file_path)
|
||||
|
||||
logging.info("OCI regions update completed successfully")
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error during OCI regions update: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user