Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/rc_supported_regions_sync.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: rc_supported_regions_sync

on:
schedule:
- cron: '0 0 * * 1' # run every Monday at midnight UTC time
workflow_dispatch: # or run on manual trigger

jobs:
rc_supported_regions_sync:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
actions: write
steps:
- name: 'Checkout'
uses: 'actions/checkout@v3'

- name: 'Install dependencies'
run: pip3 install requests

- name: 'Run rc_supported_regions_sync.py'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RC_API_KEY: ${{ secrets.RC_API_KEY }}
RC_API_SECRET_KEY: ${{ secrets.RC_API_SECRET_KEY }}
run: |
branch="rc_supported_regions_sync"
regions_change=false

# check if remote branch already exists
git fetch --all
set +e
git ls-remote --exit-code --heads origin "refs/heads/${branch}"
if [ "$?" -eq 0 ]; then
set -e
# if it does, create local branch off existing remote branch
git checkout -b "${branch}" "origin/${branch}"
git branch --set-upstream-to="origin/${branch}" "${branch}"
git pull
else
set -e
# otherwise, create local branch from main
git checkout -b "${branch}"
fi
Comment thread
cmilesb marked this conversation as resolved.

python3 build/rc_supported_regions_sync.py

regions_are_different=$(git diff data/rc_supported_regions.json)

if [[ ! -z $regions_are_different ]]; then
regions_change=true

git add "data/rc_supported_regions.json"
git config user.email "177626021+redisdocsapp[bot]@users.noreply.github.com"
git config user.name "redisdocsapp[bot]"
git commit -m "Update data/rc_supported_regions.json"
fi

if [ "$regions_change" = true ] ; then
git push origin "${branch}"

# If a pr is not already open, create one
set +e
gh search prs -R redis/docs --state open --match title "update rc supported regions" | grep -q "update rc supported regions"
if [ "$?" -eq 1 ]; then
set -e
gh pr create \
--body "update rc supported regions" \
--title "update rc supported regions" \
--head "$branch" \
--base "main"
fi
fi
152 changes: 152 additions & 0 deletions build/rc_supported_regions_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""
Syncs supported regions data from Redis Cloud API to data/rc_supported_regions.json
"""

import json
import requests
import os

RC_API_BASE_URL = "https://api.redislabs.com/v1"
OUTPUT_FILE = "data/rc_supported_regions.json"
RC_API_KEY = os.getenv("RC_API_KEY")
RC_API_SECRET_KEY = os.getenv("RC_API_SECRET_KEY")


def fetch_regions():
"""
Fetch all regions from both endpoints and merge the results.

Returns:
list: List of provider objects matching rc_supported_regions.json format
e.g. [{"provider": "aws", "regions": {"us-east-1": {...}, ...}}, ...]
"""
pro_response = requests.get(
f"{RC_API_BASE_URL}/regions",
headers={ "x-api-key": RC_API_KEY, "x-api-secret-key": RC_API_SECRET_KEY }
)
pro_response.raise_for_status()

pro_data = pro_response.json()

essentials_response = requests.get(
f"{RC_API_BASE_URL}/fixed/plans",
headers={ "x-api-key": RC_API_KEY, "x-api-secret-key": RC_API_SECRET_KEY }
)
essentials_response.raise_for_status()

essentials_data = essentials_response.json()

# Build intermediate dict for easier manipulation
regions_by_provider = {}
for region in pro_data.get("regions", []):
provider_raw = region.get("provider")
name = region.get("name")
if provider_raw and name:
provider = provider_raw.lower()
if provider not in regions_by_provider:
regions_by_provider[provider] = {}
regions_by_provider[provider][name] = {
"location": "UNKNOWN",
"area": "UNKNOWN",
"pro": True,
"essentials": False
}
Comment thread
cmilesb marked this conversation as resolved.

for plan in essentials_data.get("plans", []):
provider_raw = plan.get("provider")
name = plan.get("region")
if provider_raw and name:
provider = provider_raw.lower()
if provider not in regions_by_provider:
regions_by_provider[provider] = {}
if name not in regions_by_provider[provider]:
regions_by_provider[provider][name] = {
"location": "UNKNOWN",
"area": "UNKNOWN",
"pro": False,
"essentials": True
}
else:
regions_by_provider[provider][name]["essentials"] = True

# Sort regions within each provider
for provider in regions_by_provider:
regions_by_provider[provider] = dict(sorted(regions_by_provider[provider].items()))

# Convert to list format matching rc_supported_regions.json
result = []
for provider in sorted(regions_by_provider.keys()):
result.append({
"provider": provider,
"regions": regions_by_provider[provider]
})

return result


def get_or_create_provider_regions(data, provider):
"""
Helper to get regions dict for a provider from the list format.
If the provider doesn't exist, creates a new entry and returns its regions dict.
"""
for item in data:
if item.get("provider") == provider:
return item.get("regions", {})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detached dict silently drops new region data

Low Severity

get_or_create_provider_regions uses item.get("regions", {}) when a matching provider is found. If the provider entry exists but lacks a "regions" key, this returns a brand-new empty dict not connected to existing_data. The caller in main() would add regions to this detached dict, but those additions are silently lost when save_regions(existing_data) writes the unchanged parent structure. The "provider not found" path correctly avoids this by appending a new object and returning its regions reference.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit abd35e7. Configure here.


# Provider not found - create new entry and append to data
new_provider_obj = {"provider": provider, "regions": {}}
data.append(new_provider_obj)
return new_provider_obj["regions"]

def load_existing_regions():
"""Load the current rc_supported_regions.json file"""
with open(OUTPUT_FILE, 'r') as f:
return json.load(f)


def save_regions(data):
"""Save updated regions to rc_supported_regions.json"""
with open(OUTPUT_FILE, 'w') as f:
json.dump(data, f, indent=2)


def main():
print("Syncing supported regions...")

existing_data = load_existing_regions()
changed = False

new_data = fetch_regions()

for new_provider_obj in new_data:
provider = new_provider_obj["provider"]
new_regions = new_provider_obj["regions"]
existing_regions = get_or_create_provider_regions(existing_data, provider)

for region_id, region_info in new_regions.items():
if region_id not in existing_regions:
changed = True
existing_regions[region_id] = region_info
print(f" New region: {provider}/{region_id}")
else:
if existing_regions[region_id]["essentials"] != region_info["essentials"]:
changed = True
existing_regions[region_id]["essentials"] = region_info["essentials"]
print(f" Updated essentials for: {provider}/{region_id}")
if existing_regions[region_id]["pro"] != region_info["pro"]:
changed = True
existing_regions[region_id]["pro"] = region_info["pro"]
print(f" Updated pro for: {provider}/{region_id}")

if changed:
save_regions(existing_data)
print("Changes saved.")
else:
print("No changes detected.")

print("Done.")


if __name__ == "__main__":
main()
Loading
Loading