Skip to content

Commit 0a5a98a

Browse files
committed
Enhance ApplicationSet template in appSets.yaml.gotmpl to support upstream chart sources and improve base application handling. Update test.py to filter out None values and handle lists in application generation, validation, and export processes.
1 parent ed4ad90 commit 0a5a98a

File tree

2 files changed

+57
-118
lines changed

2 files changed

+57
-118
lines changed

appSets.yaml.gotmpl

Lines changed: 23 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ applicationsets:
162162
name: "{{`{{ .values.clusterName }}-{{ .values.appInstance }}`}}"
163163
labels:
164164
debug: "{{`{{ . | toYaml }}`}}"
165+
baseApp: "{{`{{ .baseApp }}`}}"
165166
spec:
166167
project: default # {{"{{ or .project \"default\" }}"}}
167168
destination:
@@ -173,126 +174,31 @@ applicationsets:
173174
templatePatch: |
174175
spec:
175176
sources:
176-
{{`{{- if .baseApp }}`}}
177+
# If the app is using a helmfile.yaml containing upstream charts, we need to add the upstream source
178+
{{`{{- if and (eq .appSettings.path.filename "helmfile.yaml") .releases }}`}}
179+
{{`{{- range $release := .releases }}`}}
180+
{{`{{- if eq $release.chart "./" }}`}}
181+
# Local chart source
177182
- repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
178183
targetRevision: HEAD
179-
path: {{ `apps/{{ .baseApp }}` }}
184+
path: "{{ `apps/{{ $.baseApp }}` }}"
185+
{{`{{- else }}`}}
186+
# Upstream chart source
187+
{{`{{- $repoName := (index (splitList "/" $release.chart) 0) }}`}}
188+
{{`{{- $repoURL := "" }}`}}
189+
{{`{{- range $repo := $.repositories }}`}}
190+
{{`{{- if eq $repo.name $repoName }}`}}
191+
{{`{{- $repoURL = $repo.url }}`}}
192+
{{`{{- end }}`}}
193+
{{`{{- end }}`}}
194+
- repoURL: {{`{{ $repoURL }}`}}
195+
targetRevision: {{`{{ $release.version }}`}}
196+
chart: {{`{{ $release.chart }}`}}
197+
helm:
198+
valueFiles:
199+
- {{`{{ $.values.appInstancePath }}/values.yaml`}}
200+
{{`{{- end }}`}}
180201
{{`{{- end }}`}}
181-
- repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
182-
targetRevision: HEAD
183-
path: {{`{{ .values.appInstancePath }}`}}
184-
185-
186-
root:
187-
namespace: argocd
188-
finalizers:
189-
- resources-finalizer.argocd.argoproj.io
190-
goTemplate: true
191-
goTemplateOptions: ["missingkey=invalid"]
192-
193-
generators:
194-
- matrix:
195-
generators:
196-
# Original app deployment generators
197-
- matrix:
198-
generators:
199-
- clusters:
200-
selectors:
201-
values:
202-
clusterGroup: "{{`{{ or .metadata.labels.clusterGroup nil }}`}}"
203-
clusterName: "{{`{{ or .metadata.labels.clusterId .name }}`}}"
204-
clusterSlug: "{{`{{ if .metadata.labels.clusterGroup }}{{ .metadata.labels.clusterGroup }}-{{ end }}{{ or .metadata.labels.clusterId .name }}`}}"
205-
206-
- git:
207-
repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
208-
revision: HEAD
209-
directories:
210-
# global - on every cluster
211-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}/apps/*"
212-
# per clusterGroup - on every cluster in a group
213-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterGroup }}/apps/*`}}"
214-
# on a specific single cluster, either grouped or ungrouped
215-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterGroup }}/{{ .values.clusterName }}/apps/*`}}"
216-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterName }}/apps/*`}}"
217-
values:
218-
appInstance: '{{ printf "%s" "{{- printf `{{ .path.basename }}` -}}" }}'
219-
appInstancePath: '{{ printf "%s" "{{- printf `{{ .path.path }}` -}}" }}'
220-
221-
- merge:
222-
mergeKeys:
223-
- values
224-
generators:
225-
- git:
226-
repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
227-
revision: HEAD
228-
files:
229-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}/config.yaml" # 2. Universe globals
230-
values:
231-
selector: config
232-
233-
- git:
234-
repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
235-
revision: HEAD
236-
files:
237-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterGroup }}/config.yaml`}}" # 3. Constellation globals
238-
values:
239-
selector: config
240-
241-
- git:
242-
repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
243-
revision: HEAD
244-
files:
245-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterName }}/config.yaml`}}" # 4. Cluster globals
246-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterGroup }}/{{ .values.clusterName }}/config.yaml`}}" # 4. Cluster globals
247-
values:
248-
selector: config
249-
250-
- git:
251-
repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
252-
revision: HEAD
253-
files:
254-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/apps/{{ .values.appInstance }}/config.yaml`}}" # 5. Universe-wide app settings
255-
values:
256-
selector: config
257-
258-
- git:
259-
repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
260-
revision: HEAD
261-
files:
262-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterGroup }}/apps/{{ .values.appInstance }}/config.yaml`}}" # 6. Constellation-wide app settings
263-
values:
264-
selector: config
265-
266-
- git:
267-
repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
268-
revision: HEAD
269-
files:
270-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterName }}/apps/{{ .values.appInstance }}/config.yaml`}}" # 7. Cluster-specific app settings
271-
- path: "{{ .Values | get "baseDir" "baseDir missing" }}{{`/{{ .values.clusterGroup }}/{{ .values.clusterName }}/apps/{{ .values.appInstance }}/config.yaml`}}" # 7. Cluster-specific app settings
272-
values:
273-
selector: config
274-
selector:
275-
matchExpressions:
276-
- key: baseApp
277-
operator: Exists
278-
template:
279-
metadata:
280-
name: "{{`{{ .values.clusterName }}-{{ .values.appInstance }}`}}"
281-
spec:
282-
project: default # {{"{{ or .project \"default\" }}"}}
283-
destination:
284-
name: "{{`{{ .values.clusterSlug }}`}}"
285-
namespace: "{{`{{ or .namespace .values.appInstance }}`}}"
286-
syncPolicy:
287-
syncOptions:
288-
- CreateNamespace=true
289-
templatePatch: |
290-
spec:
291-
sources:
292-
{{`{{- if .baseApp }}`}}
293-
- repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
294-
targetRevision: HEAD
295-
path: {{ `apps/{{ .baseApp }}` }}
296202
{{`{{- end }}`}}
297203
- repoURL: {{ .Values | get "repoUrl" "repoUrl missing"}}
298204
targetRevision: HEAD

bin/test.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,33 @@ def generate_applications(appset_file: str) -> List[Dict[str, Any]]:
5555
print(f"Error generating applications: {result.stderr}")
5656
return []
5757

58-
return list(yaml.safe_load_all(result.stdout))
58+
# Parse all YAML documents from the output
59+
yaml_docs = list(yaml.safe_load_all(result.stdout))
60+
61+
# Filter out None values and ensure all items are dictionaries
62+
valid_apps = []
63+
for doc in yaml_docs:
64+
if doc is None:
65+
continue
66+
67+
# If the document is a list, extract each item
68+
if isinstance(doc, list):
69+
for item in doc:
70+
if item is not None and isinstance(item, dict):
71+
valid_apps.append(item)
72+
# If the document is a dictionary, add it directly
73+
elif isinstance(doc, dict):
74+
valid_apps.append(doc)
75+
76+
return valid_apps
5977

6078
class ApplicationValidator:
6179
"""Handles validation of Applications and ApplicationSets."""
6280

6381
@staticmethod
6482
def validate_applications(apps: List[Dict[str, Any]], validation_rules: Optional[Dict[str, Callable]] = None) -> bool:
6583
"""Validate the generated applications against predefined rules."""
84+
# Filter out None values and ensure all items are dictionaries
6685
apps = [app for app in apps if app is not None]
6786
if not apps:
6887
print("No valid applications to validate")
@@ -72,6 +91,11 @@ def validate_applications(apps: List[Dict[str, Any]], validation_rules: Optional
7291
valid = True
7392

7493
for i, app in enumerate(apps):
94+
# Check if app is a list and handle it appropriately
95+
if isinstance(app, list):
96+
print(f"Application {i+1}: WARNING - Found a list instead of a dictionary, skipping validation")
97+
continue
98+
7599
print(f"Application {i+1}: {app.get('metadata', {}).get('name', 'unknown')}")
76100
valid = valid and ApplicationValidator._validate_single_application(app, i)
77101

@@ -153,6 +177,11 @@ def export_applications(apps: List[Dict[str, Any]], output_dir: str) -> None:
153177
return
154178

155179
for i, app in enumerate(apps):
180+
# Skip if app is a list instead of a dictionary
181+
if isinstance(app, list):
182+
print(f"Skipping export of application {i+1}: Found a list instead of a dictionary")
183+
continue
184+
156185
name = app.get("metadata", {}).get("name", f"app-{i+1}")
157186
output_file = os.path.join(output_dir, f"{name}.yaml")
158187
with open(output_file, "w") as f:
@@ -179,6 +208,10 @@ def validate_plain_appset_sources(apps: List[Dict[str, Any]]) -> bool:
179208
valid = True
180209

181210
for app in apps:
211+
# Skip if app is a list instead of a dictionary
212+
if isinstance(app, list):
213+
continue
214+
182215
if not app.get("metadata", {}).get("name", "").startswith("in-cluster-"):
183216
continue
184217

0 commit comments

Comments
 (0)