Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
29 changes: 17 additions & 12 deletions .github/workflows/validate-charts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,22 @@ jobs:
for chart in charts/*/; do
chart_name=$(basename "${chart}")
echo "::group::Rendering ${chart_name}..."
# countly chart requires secrets — provide dummy values for template validation
if [ "${chart_name}" = "countly" ]; then
helm template test-release "${chart}" \
--set secrets.common.countlyEncryptionKey=test \
--set secrets.common.countlyTokenSecret=test \
--set secrets.clickhouse.password=test \
--set secrets.mongodb.password=test \
> /dev/null || exit_code=1
else
helm template test-release "${chart}" > /dev/null || exit_code=1
fi
set_args=""
case "${chart_name}" in
countly)
set_args="--set secrets.common.encryptionReportsKey=test --set secrets.common.webSessionSecret=test --set secrets.common.passwordSecret=test --set secrets.clickhouse.password=test --set secrets.mongodb.password=test"
;;
countly-clickhouse)
set_args="--set auth.defaultUserPassword.password=test"
;;
countly-kafka)
set_args="--set kafkaConnect.clickhouse.password=test"
;;
countly-mongodb)
set_args="--set users.app.password=test --set users.metrics.password=test"
;;
esac
helm template test-release "${chart}" ${set_args} > /dev/null || exit_code=1
echo "::endgroup::"
done
exit $exit_code
Expand Down Expand Up @@ -156,7 +161,7 @@ jobs:
run: |
for chart in charts/*/Chart.yaml; do
chart_name=$(basename "$(dirname "${chart}")")
version=$(grep '^version:' "${chart}" | awk '{print $2}' | tr -d '"'"'"')
version=$(grep '^version:' "${chart}" | awk '{print $2}' | tr -d "\"'")
if [ -z "${version}" ]; then
echo "::error::Missing version in ${chart}"
exit 1
Expand Down
20 changes: 20 additions & 0 deletions charts/countly-argocd/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v2
name: countly-argocd
description: ArgoCD app-of-apps for deploying Countly to one or more clusters
type: application
version: 0.1.0
appVersion: "1.0.0"
home: https://countly.com
icon: https://count.ly/images/logos/countly-logo.svg
sources:
- https://github.com/Countly/countly-server
keywords:
- argocd
- gitops
- countly
- multi-cluster
maintainers:
- name: Countly
url: https://countly.com
annotations:
artifacthub.io/license: AGPL-3.0
142 changes: 142 additions & 0 deletions charts/countly-argocd/examples/applicationset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Alternative to the app-of-apps chart for 100+ customers.
#
# Instead of running `helm install` per customer, this single ApplicationSet
# generates all Applications from a list of customers. Add a new customer
# by adding an entry to the list — no new Helm release needed.
#
# Prerequisites:
# 1. ArgoCD ApplicationSet controller installed
# 2. Target clusters registered with ArgoCD
# 3. Environment directories exist per customer in the helm repo
# 4. Custom health checks in argocd-cm (see chart NOTES.txt)
#
# Apply: kubectl apply -f applicationset.yaml -n argocd
# Add customer: add entry to generators[].list.elements, re-apply

# One ApplicationSet per component, each with the correct sync-wave.
# ArgoCD processes waves within a parent sync — use an app-of-apps
# root Application that points to a directory containing these files.

---
# Wave 0: MongoDB
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: countly-mongodb
namespace: argocd
spec:
generators:
- list:
elements:
- customer: customer-a
server: https://cluster-a.example.com
sizing: production
security: hardened
- customer: customer-b
server: https://cluster-b.example.com
sizing: small
security: open
# Add more customers here...
template:
metadata:
name: "{{customer}}-mongodb"
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
project: "{{customer}}"
source:
repoURL: https://github.com/Countly/helm.git
targetRevision: main
path: charts/countly-mongodb
helm:
releaseName: countly-mongodb
valueFiles:
- "../../environments/{{customer}}/global.yaml"
- "../../profiles/sizing/{{sizing}}/mongodb.yaml"
- "../../profiles/security/{{security}}/mongodb.yaml"
- "../../environments/{{customer}}/mongodb.yaml"
parameters:
- name: argocd.enabled
value: "true"
destination:
server: "{{server}}"
namespace: mongodb
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m

---
# Wave 0: ClickHouse (same pattern as MongoDB)
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: countly-clickhouse
namespace: argocd
spec:
generators:
- list:
elements:
- customer: customer-a
server: https://cluster-a.example.com
sizing: production
security: hardened
- customer: customer-b
server: https://cluster-b.example.com
sizing: small
security: open
template:
metadata:
name: "{{customer}}-clickhouse"
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
project: "{{customer}}"
source:
repoURL: https://github.com/Countly/helm.git
targetRevision: main
path: charts/countly-clickhouse
helm:
releaseName: countly-clickhouse
valueFiles:
- "../../environments/{{customer}}/global.yaml"
- "../../profiles/sizing/{{sizing}}/clickhouse.yaml"
- "../../profiles/security/{{security}}/clickhouse.yaml"
- "../../environments/{{customer}}/clickhouse.yaml"
parameters:
- name: argocd.enabled
value: "true"
destination:
server: "{{server}}"
namespace: clickhouse
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m

# Repeat the same pattern for:
# - countly-kafka (wave 5)
# - countly (wave 10)
# - countly-observability (wave 15)
# - countly-migrations (wave 10, optional)
#
# For a DRY approach, use a Matrix generator combining the customer list
# with a component list to generate all Applications from a single spec.
49 changes: 49 additions & 0 deletions charts/countly-argocd/examples/multi-cluster.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Example: Deploy Countly to two clusters from a single ArgoCD instance.
#
# Prerequisites:
# 1. ArgoCD installed on a management cluster
# 2. Target clusters registered with ArgoCD:
# argocd cluster add cluster-a-context
# argocd cluster add cluster-b-context
# 3. Environment directories exist:
# environments/customer-a/ (global.yaml, mongodb.yaml, etc.)
# environments/customer-b/ (global.yaml, mongodb.yaml, etc.)
# 4. Custom health checks configured in argocd-cm (see NOTES.txt)
#
# Deploy:
# helm install customer-a charts/countly-argocd -f examples/multi-cluster.yaml -n argocd
# helm install customer-b charts/countly-argocd -f examples/multi-cluster.yaml --set environment=customer-b -n argocd

# --- Customer A: Production, large, with migrations ---
repoURL: "https://github.com/Countly/helm.git"
targetRevision: main
environment: customer-a
project: countly-customer-a

destination:
server: "https://cluster-a.example.com"

global:
sizing: production
security: hardened
tls: letsencrypt
observability: full
kafkaConnect: throughput

mongodb:
enabled: true
clickhouse:
enabled: true
kafka:
enabled: true
countly:
enabled: true
observability:
enabled: true
migrations:
enabled: true # This customer needs data migration

syncPolicy:
automated: true
selfHeal: true
prune: true
71 changes: 71 additions & 0 deletions charts/countly-argocd/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
=== Countly ArgoCD Deployment ===

Environment: {{ .Values.environment }}
Cluster: {{ .Values.destination.server }}
Project: {{ include "countly-argocd.projectName" . }}

Applications deployed (sync wave order):
Wave 0: {{ if .Values.mongodb.enabled }}mongodb{{ end }} {{ if .Values.clickhouse.enabled }}clickhouse{{ end }}
Wave 5: {{ if .Values.kafka.enabled }}kafka{{ end }}
Wave 10: {{ if .Values.countly.enabled }}countly{{ end }} {{ if .Values.migrations.enabled }}migrations{{ end }}
Wave 15: {{ if .Values.observability.enabled }}observability{{ end }}

--- Status ---

# List all Countly applications
kubectl get applications -n argocd -l app.kubernetes.io/instance={{ .Release.Name }}

# Sync all
argocd app sync -l app.kubernetes.io/instance={{ .Release.Name }}

--- Multi-Cluster ---

# Deploy to another cluster
helm install countly-<cluster> charts/countly-argocd \
--set environment=<env-name> \
--set destination.server=https://<cluster-api> \
-n argocd

--- Required: ArgoCD Custom Health Checks ---

Add these to your argocd-cm ConfigMap for sync waves to
block on actual readiness:

resource.customizations.health.kafka.strimzi.io_Kafka: |
hs = {}
if obj.status ~= nil and obj.status.conditions ~= nil then
for _, c in ipairs(obj.status.conditions) do
if c.type == "Ready" and c.status == "True" then
hs.status = "Healthy"; hs.message = c.message or "Ready"; return hs
end
if c.type == "NotReady" then
hs.status = "Progressing"; hs.message = c.message or "Not ready"; return hs
end
end
end
hs.status = "Progressing"; hs.message = "Waiting for status"; return hs

# Same pattern for: KafkaConnect, KafkaNodePool, KafkaConnector

resource.customizations.health.clickhouse.com_ClickHouseCluster: |
hs = {}
if obj.status ~= nil and obj.status.status ~= nil then
if obj.status.status == "Completed" then
hs.status = "Healthy"; hs.message = "Completed"; return hs
end
end
hs.status = "Progressing"; hs.message = "Provisioning"; return hs

resource.customizations.health.mongodbcommunity.mongodb.com_MongoDBCommunity: |
hs = {}
if obj.status ~= nil and obj.status.phase ~= nil then
if obj.status.phase == "Running" then
hs.status = "Healthy"; hs.message = "Running"; return hs
end
end
hs.status = "Progressing"; hs.message = "Provisioning"; return hs

--- Teardown ---

helm uninstall {{ .Release.Name }} -n argocd
# Cascading finalizers will delete all child Applications
62 changes: 62 additions & 0 deletions charts/countly-argocd/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "countly-argocd.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "countly-argocd.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "countly-argocd.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
app.kubernetes.io/name: {{ include "countly-argocd.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
ArgoCD project name — unique per release to prevent multi-tenant collisions.
*/}}
{{- define "countly-argocd.projectName" -}}
{{- .Values.project | default (include "countly-argocd.fullname" .) }}
{{- end -}}

{{/*
Sync policy block — reused by all Application templates.
Includes retry policy for resilience at scale (100+ customers = 600+ Applications).
*/}}
{{- define "countly-argocd.syncPolicy" -}}
syncPolicy:
{{- if .Values.syncPolicy.automated }}
automated:
prune: {{ .Values.syncPolicy.prune }}
selfHeal: {{ .Values.syncPolicy.selfHeal }}
{{- end }}
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
- RespectIgnoreDifferences=true
retry:
limit: {{ .Values.syncPolicy.retry.limit }}
backoff:
duration: {{ .Values.syncPolicy.retry.backoff.duration }}
factor: {{ .Values.syncPolicy.retry.backoff.factor }}
maxDuration: {{ .Values.syncPolicy.retry.backoff.maxDuration }}
{{- end -}}
Loading
Loading