Skip to content

Commit 9745ddf

Browse files
committed
update egress
1 parent b53c22e commit 9745ddf

File tree

3 files changed

+273
-3
lines changed

3 files changed

+273
-3
lines changed

charts/ctrlplane/charts/workspace-engine/templates/statefulset.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ spec:
5757
fieldPath: metadata.namespace
5858

5959
- name: REGISTER_ADDRESS
60-
value: "http://$(POD_NAME).{{ include "workspace-engine.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:8081"
61-
- name: ROUTER_ADDRESS
60+
value: "http://$(POD_NAME).{{ .Release.Name }}-engine.{{ .Release.Namespace }}.svc.cluster.local:8081"
61+
- name: ROUTER_URL
6262
value: "http://{{ .Release.Name }}-workspace-engine-router.{{ .Release.Namespace }}.svc.cluster.local:9091"
6363

6464
- name: POSTGRES_URL

charts/ctrlplane/templates/ingress.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ spec:
2525
path: /
2626
backend:
2727
service:
28-
name: {{ .Release.Name }}-webservice
28+
name: {{ .Release.Name }}-web
2929
port:
3030
number: 3000
31+
- pathType: Prefix
32+
path: /api
33+
backend:
34+
service:
35+
name: {{ .Release.Name }}-api
36+
port:
37+
number: 8081
3138
- pathType: Prefix
3239
path: /api/v1/resources/proxy
3340
backend:

update-image-tags.py

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to fetch the latest Docker image tags from Docker Hub and update local-values.yaml
4+
"""
5+
6+
import argparse
7+
import json
8+
import re
9+
import sys
10+
from pathlib import Path
11+
from typing import Dict, List, Optional, Set
12+
13+
import requests
14+
import yaml
15+
16+
17+
class DockerHubAPI:
18+
"""Docker Hub API client"""
19+
20+
BASE_URL = "https://hub.docker.com/v2"
21+
22+
def get_latest_tag(self, repository: str, namespace: str = "ctrlplane") -> Optional[str]:
23+
"""
24+
Get the latest tag for a Docker Hub repository.
25+
26+
Args:
27+
repository: Repository name (e.g., 'api', 'web')
28+
namespace: Docker Hub namespace (default: 'ctrlplane')
29+
30+
Returns:
31+
Latest tag or None if not found
32+
"""
33+
url = f"{self.BASE_URL}/repositories/{namespace}/{repository}/tags"
34+
params = {
35+
"page_size": 100,
36+
"ordering": "last_updated" # This gets the most recently updated tags first
37+
}
38+
39+
try:
40+
response = requests.get(url, params=params, timeout=10)
41+
response.raise_for_status()
42+
43+
data = response.json()
44+
tags = data.get("results", [])
45+
46+
if not tags:
47+
print(f"Warning: No tags found for {namespace}/{repository}")
48+
return None
49+
50+
# Debug: show first few tags to understand what we're getting
51+
print(f" First few tags for {namespace}/{repository}:")
52+
for i, tag in enumerate(tags[:5]):
53+
print(f" {i+1}. {tag['name']} (updated: {tag.get('last_updated', 'unknown')})")
54+
55+
# Filter out 'latest' tag and look for commit-like tags (short hashes)
56+
# Since we're ordering by -last_updated, the first matching tag is the newest
57+
for tag in tags:
58+
tag_name = tag["name"]
59+
if tag_name != "latest" and self._is_commit_tag(tag_name):
60+
print(f" Selected newest commit tag: {tag_name}")
61+
return tag_name
62+
63+
# If no commit-like tag found, return the first non-latest tag
64+
for tag in tags:
65+
tag_name = tag["name"]
66+
if tag_name != "latest":
67+
print(f" Selected newest non-latest tag: {tag_name}")
68+
return tag_name
69+
70+
print(f"Warning: No suitable tags found for {namespace}/{repository}")
71+
return None
72+
73+
except requests.RequestException as e:
74+
print(f"Error fetching tags for {namespace}/{repository}: {e}")
75+
return None
76+
77+
def _is_commit_tag(self, tag: str) -> bool:
78+
"""Check if tag looks like a git commit hash (6-8 alphanumeric characters)"""
79+
return bool(re.match(r'^[a-f0-9]{6,8}$', tag))
80+
81+
82+
class LocalValuesUpdater:
83+
"""Updates local-values.yaml with new image tags"""
84+
85+
def __init__(self, values_file: Path, only_services: Optional[Set[str]] = None):
86+
self.values_file = values_file
87+
self.docker_api = DockerHubAPI()
88+
self.only_services = only_services
89+
90+
# Map of service names to their Docker Hub repository names
91+
self.service_to_repo = {
92+
"web": "web",
93+
"api": "api",
94+
"workspace-engine-router": "workspace-engine-router",
95+
"workspace-engine": "workspace-engine",
96+
"event-queue": "event-queue",
97+
"event-worker": "event-worker", # Note: not in values files but in local-values
98+
"jobs": "jobs",
99+
"migrations": "migrations",
100+
"pty-proxy": "pty-proxy",
101+
"webservice": "webservice"
102+
}
103+
104+
def load_values(self) -> Dict:
105+
"""Load the current values from local-values.yaml"""
106+
try:
107+
with open(self.values_file, 'r') as f:
108+
return yaml.safe_load(f) or {}
109+
except FileNotFoundError:
110+
print(f"Error: {self.values_file} not found")
111+
sys.exit(1)
112+
except yaml.YAMLError as e:
113+
print(f"Error parsing YAML: {e}")
114+
sys.exit(1)
115+
116+
def save_values(self, values: Dict) -> None:
117+
"""Save the updated values to local-values.yaml"""
118+
try:
119+
with open(self.values_file, 'w') as f:
120+
yaml.dump(values, f, default_flow_style=False, sort_keys=False)
121+
print(f"Successfully updated {self.values_file}")
122+
except Exception as e:
123+
print(f"Error saving values: {e}")
124+
sys.exit(1)
125+
126+
def get_available_services(self) -> List[str]:
127+
"""Get list of available services that can be updated"""
128+
return list(self.service_to_repo.keys())
129+
130+
def validate_services(self, services: Set[str]) -> None:
131+
"""Validate that all requested services are available"""
132+
available_services = set(self.service_to_repo.keys())
133+
invalid_services = services - available_services
134+
135+
if invalid_services:
136+
print(f"Error: Unknown services: {', '.join(sorted(invalid_services))}")
137+
print(f"Available services: {', '.join(sorted(available_services))}")
138+
sys.exit(1)
139+
140+
def update_image_tags(self) -> None:
141+
"""Update image tags in local-values.yaml"""
142+
print("Loading current values...")
143+
values = self.load_values()
144+
145+
# Determine which services to update
146+
services_to_update = self.service_to_repo.items()
147+
if self.only_services:
148+
self.validate_services(self.only_services)
149+
services_to_update = [(name, repo) for name, repo in self.service_to_repo.items()
150+
if name in self.only_services]
151+
print(f"Updating only: {', '.join(sorted(self.only_services))}")
152+
else:
153+
print("Updating all services...")
154+
155+
updated_services = []
156+
157+
for service_name, repo_name in services_to_update:
158+
if service_name in values and isinstance(values[service_name], dict):
159+
print(f"\nChecking {service_name}...")
160+
161+
# Get latest tag from Docker Hub
162+
latest_tag = self.docker_api.get_latest_tag(repo_name)
163+
164+
if latest_tag:
165+
# Ensure image section exists
166+
if "image" not in values[service_name]:
167+
values[service_name]["image"] = {}
168+
169+
# Get current tag
170+
current_tag = values[service_name]["image"].get("tag", "unknown")
171+
172+
# Update tag if different
173+
if current_tag != latest_tag:
174+
values[service_name]["image"]["tag"] = latest_tag
175+
updated_services.append(f"{service_name}: {current_tag} -> {latest_tag}")
176+
print(f" Updated {service_name}: {current_tag} -> {latest_tag}")
177+
else:
178+
print(f" {service_name} already up to date: {current_tag}")
179+
else:
180+
print(f" Could not fetch latest tag for {service_name}")
181+
elif self.only_services and service_name in self.only_services:
182+
print(f"\nWarning: Service '{service_name}' not found in local-values.yaml")
183+
184+
if updated_services:
185+
print(f"\nUpdating {len(updated_services)} services:")
186+
for update in updated_services:
187+
print(f" - {update}")
188+
189+
self.save_values(values)
190+
else:
191+
print("\nNo updates needed - all services are up to date!")
192+
193+
194+
def parse_args():
195+
"""Parse command line arguments"""
196+
parser = argparse.ArgumentParser(
197+
description="Update Docker image tags in local-values.yaml from Docker Hub",
198+
formatter_class=argparse.RawDescriptionHelpFormatter,
199+
epilog="""
200+
Examples:
201+
%(prog)s # Update all services
202+
%(prog)s --only web,api # Update only web and api services
203+
%(prog)s --list # List available services
204+
"""
205+
)
206+
207+
parser.add_argument(
208+
"--only",
209+
type=str,
210+
help="Comma-separated list of services to update (e.g., 'web,api,jobs')"
211+
)
212+
213+
parser.add_argument(
214+
"--list",
215+
action="store_true",
216+
help="List available services and exit"
217+
)
218+
219+
return parser.parse_args()
220+
221+
222+
def main():
223+
"""Main entry point"""
224+
args = parse_args()
225+
226+
script_dir = Path(__file__).parent
227+
values_file = script_dir / "charts" / "ctrlplane" / "local-values.yaml"
228+
229+
if not values_file.exists():
230+
print(f"Error: {values_file} does not exist")
231+
sys.exit(1)
232+
233+
# Create updater to get available services
234+
updater = LocalValuesUpdater(values_file)
235+
236+
# Handle --list option
237+
if args.list:
238+
print("Available services:")
239+
for service in sorted(updater.get_available_services()):
240+
print(f" - {service}")
241+
return
242+
243+
# Parse --only option
244+
only_services = None
245+
if args.only:
246+
only_services = set(service.strip() for service in args.only.split(','))
247+
if not only_services:
248+
print("Error: --only requires at least one service name")
249+
sys.exit(1)
250+
251+
# Update the updater with the filtered services
252+
updater = LocalValuesUpdater(values_file, only_services)
253+
254+
print(f"Updating image tags in {values_file}")
255+
print("=" * 50)
256+
257+
updater.update_image_tags()
258+
259+
print("\nDone!")
260+
261+
262+
if __name__ == "__main__":
263+
main()

0 commit comments

Comments
 (0)