Skip to content
Merged
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
4 changes: 2 additions & 2 deletions internal/cli/adopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func reason(i detect.Install) string {
// an RTE tells its version only with its token
key = "detect.why_unmanagedRunner"
}
return i18n.T(key, "dir", i.Dir, "port", i.Port, "version", orStr(i.Version, i18n.T("detect.versionUnknown")))
return i18n.T(key, "dir", i.Dir, "port", i.Port, "version", orStr(i.Version, i18n.T("detect.versionUnknown")), "app", orStr(i.ArgoApp, "?"))
}

// foundLine is an installation found on one line: what and where, and why it cannot be
Expand All @@ -98,7 +98,7 @@ func foundLine(i detect.Install) string {
if i.Adoptable() || i.Problem == detect.Unmanaged {
return describe(i)
}
return describe(i) + ": " + i18n.T("detect.short_"+i.Problem)
return describe(i) + ": " + i18n.T("detect.short_"+i.Problem, "app", orStr(i.ArgoApp, "?"))
}

// foundAdvice is what to do about the installations that cannot be taken over: the advice
Expand Down
4 changes: 4 additions & 0 deletions internal/detect/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ const (
NativeLayout = "nativeLayout"
// on Kubernetes, not a release of the ansibleforms chart (or helm is missing here)
NoHelm = "noHelm"
// on Kubernetes, managed by an Argo CD Application : upgraded there, never from here
ArgoCD = "argoCD"
// its version could not be read
UnknownVersion = "unknownVersion"
// it answers on a port, and no engine manages it
Expand Down Expand Up @@ -96,6 +98,8 @@ type Install struct {
Namespace string
Release string
Chart string
// ArgoApp is the Argo CD Application that manages it (its Problem ArgoCD), when known
ArgoApp string
Workloads []string

// Database: a Runner of 7.2 in the deprecated database mode (an RTE alone in its compose
Expand Down
28 changes: 28 additions & 0 deletions internal/detect/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,3 +560,31 @@ func TestProbePort(t *testing.T) {
t.Errorf("nothing listening: %+v", a)
}
}

// workloads Argo CD manages are found, named after their Application, and never taken over:
// by its tracking annotation, and by the chart's labels without a Helm release (label tracking)
func TestFindArgoCD(t *testing.T) {
f := &fake{t: t, has: map[string]bool{"kubectl": true}, out: map[string]string{
"kubectl get deployments,statefulsets": `{"items":[
{"kind":"Deployment","metadata":{"name":"forms-server","namespace":"forms","labels":{"app.kubernetes.io/instance":"forms"},
"annotations":{"argocd.argoproj.io/tracking-id":"ansibleforms-prod:apps/Deployment:forms/forms-server"}},
"spec":{"template":{"spec":{"containers":[{"image":"ghcr.io/ansibleforms/ansibleforms:7.3.0"}]}}},"status":{"readyReplicas":1}},
{"kind":"Deployment","metadata":{"name":"lab-server","namespace":"lab","labels":{"app.kubernetes.io/instance":"lab-forms","app.kubernetes.io/managed-by":"Helm"}},
"spec":{"template":{"spec":{"containers":[{"image":"ghcr.io/ansibleforms/ansibleforms:7.2.0"}]}}},"status":{"readyReplicas":1}}
]}`,
}}
got := find(context.Background(), f.env())
if len(got) != 2 {
t.Fatalf("found %+v, want 2", got)
}
apps := map[string]string{}
for _, i := range got {
if i.Problem != ArgoCD || i.Adoptable() {
t.Errorf("%s: problem %q, want argoCD and not taken over", i.Namespace, i.Problem)
}
apps[i.Namespace] = i.ArgoApp
}
if apps["forms"] != "ansibleforms-prod" || apps["lab"] != "lab-forms" {
t.Errorf("applications %v", apps)
}
}
43 changes: 39 additions & 4 deletions internal/detect/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"regexp"
"sort"
"strings"
)

// ─── Kubernetes ───────────────────────────────────────────────────────────────
Expand All @@ -23,9 +24,10 @@ type workloads struct {
Items []struct {
Kind string `json:"kind"`
Metadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Labels map[string]string `json:"labels"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
} `json:"metadata"`
Spec struct {
Template struct {
Expand All @@ -42,6 +44,31 @@ type workloads struct {
} `json:"items"`
}

// argoApp tells whether Argo CD manages a workload, and its Application's name when it says.
// Argo CD marks what it applies with the tracking-id annotation (app:group/kind:ns/name) or
// the argocd.argoproj.io/instance label ; with its default label tracking it leaves only the
// chart's app.kubernetes.io/instance and managed-by Helm, without a Helm release in the
// cluster (it renders the chart itself) : the caller decides that case, knowing the releases.
//
// Args:
//
// labels: the workload's labels.
// annotations: its annotations.
//
// Returns:
//
// The Application's name ("" when unknown), and whether Argo CD marks it.
func argoApp(labels, annotations map[string]string) (string, bool) {
if id := annotations["argocd.argoproj.io/tracking-id"]; id != "" {
name, _, _ := strings.Cut(id, ":")
return name, true
}
if name := labels["argocd.argoproj.io/instance"]; name != "" {
return name, true
}
return "", false
}

// chartRe cuts helm's chart column (ansibleforms-7.2.0) into its name and its version
var chartRe = regexp.MustCompile(`^(.+)-v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$`)

Expand Down Expand Up @@ -132,9 +159,17 @@ func kubernetes(ctx context.Context, env Env) []Install {
if kind != "app" {
role = RoleRunner
}
// rendered from the chart (managed-by Helm) with no Helm release : Argo CD, which
// renders the chart itself and tracks it by its instance label
problem, app := NoHelm, ""
if name, ok := argoApp(md.Labels, md.Annotations); ok {
problem, app = ArgoCD, name
} else if md.Labels["app.kubernetes.io/managed-by"] == "Helm" && instance != "" {
problem, app = ArgoCD, instance
}
byRelease[key] = len(found)
found = append(found, Install{Engine: Kubernetes, Role: role, Version: version, Running: item.Status.ReadyReplicas > 0,
Namespace: md.Namespace, Release: group, Workloads: []string{name}, Problem: NoHelm, key: key})
Namespace: md.Namespace, Release: group, Workloads: []string{name}, Problem: problem, ArgoApp: app, key: key})
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions internal/i18n/locales/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"detect.why_runnerLayout": "Aquest Runner no el va configurar l'instal·lador.\nActualitzeu-lo a mà, o instal·leu un nou Runner amb l'instal·lador i aproveu-lo a AnsibleForms.",
"detect.why_nativeLayout": "La seva carpeta no segueix l'estructura de l'instal·lador (releases/, current, app.env i rte.env).\nActualitzeu-lo a mà, de la mateixa manera que es va instal·lar.",
"detect.why_noHelm": "S'executa a Kubernetes, però no com a release del chart Helm ansibleforms (o aquí falta helm).\nActualitzeu-lo amb l'eina que el va desplegar.",
"detect.why_argoCD": "S'executa a Kubernetes, gestionat per l'Application d'Argo CD {app}.\nL'instal·lador no hi toca: actualitza'l a Argo CD, canviant la versió del chart de l'Application.",
"detect.why_unknownVersion": "No s'ha pogut llegir la seva versió.\nInicieu-lo perquè respongui al seu port i torneu a executar l'instal·lador.",
"detect.why_unmanaged": "AnsibleForms {version} respon al port {port}, però cap motor no el gestiona (executat des del codi o a mà).\nL'instal·lador no el pot actualitzar. Una nova instal·lació podria xocar amb els seus ports o la seva base de dades.",
"detect.why_unmanagedRunner": "Un RTE d'AnsibleForms respon al port {port}, però cap motor no el gestiona (executat des del codi o a mà).\nL'instal·lador no el pot actualitzar. Una nova instal·lació podria xocar amb el seu port.",
Expand All @@ -322,6 +323,7 @@
"detect.short_runnerLayout": "aquest Runner no el va instal·lar l'instal·lador",
"detect.short_nativeLayout": "la seva carpeta no segueix l'estructura de l'instal·lador",
"detect.short_noHelm": "no és una release del chart Helm d’ansibleforms",
"detect.short_argoCD": "gestionat per Argo CD (Application {app})",
"detect.short_unknownVersion": "no se’n pot llegir la versió",
"detect.short_unmanaged": "cap motor no el gestiona",
"migrate.gone": "El contenidor RTE del Runner de {dir} ja no hi és, així que no es poden llegir el nom ni el testimoni del seu runner.\nTorneu-lo a iniciar, o instal·leu un Runner nou i aproveu-lo a AnsibleForms.",
Expand Down
2 changes: 2 additions & 0 deletions internal/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"detect.why_runnerLayout": "Dieser Runner wurde nicht vom Installer eingerichtet.\nAktualisieren Sie ihn von Hand, oder installieren Sie mit dem Installer einen neuen Runner und genehmigen Sie ihn in AnsibleForms.",
"detect.why_nativeLayout": "Sein Ordner folgt nicht dem Aufbau des Installers (releases/, current, app.env und rte.env).\nAktualisieren Sie es von Hand, so wie es installiert wurde.",
"detect.why_noHelm": "Es läuft auf Kubernetes, aber nicht als Release des Helm-Charts ansibleforms (oder helm fehlt hier).\nAktualisieren Sie es mit dem Werkzeug, das es bereitgestellt hat.",
"detect.why_argoCD": "Es läuft auf Kubernetes, verwaltet von der Argo-CD-Application {app}.\nDer Installer lässt es unverändert: Aktualisiere es in Argo CD, indem du die Chart-Version der Application änderst.",
"detect.why_unknownVersion": "Seine Version konnte nicht gelesen werden.\nStarten Sie es, damit es auf seinem Port antwortet, und führen Sie den Installer erneut aus.",
"detect.why_unmanaged": "AnsibleForms {version} antwortet auf Port {port}, aber keine Engine verwaltet es (aus dem Quellcode oder von Hand gestartet).\nDer Installer kann es nicht aktualisieren. Eine neue Installation könnte mit seinen Ports oder seiner Datenbank kollidieren.",
"detect.why_unmanagedRunner": "Ein AnsibleForms-RTE antwortet auf Port {port}, aber keine Engine verwaltet es (aus dem Quellcode oder von Hand gestartet).\nDer Installer kann es nicht aktualisieren. Eine neue Installation könnte mit seinem Port kollidieren.",
Expand All @@ -322,6 +323,7 @@
"detect.short_runnerLayout": "dieser Runner wurde nicht vom Installer eingerichtet",
"detect.short_nativeLayout": "sein Ordner hat nicht den Aufbau des Installers",
"detect.short_noHelm": "kein Release des ansibleforms-Helm-Charts",
"detect.short_argoCD": "verwaltet von Argo CD (Application {app})",
"detect.short_unknownVersion": "seine Version ist nicht lesbar",
"detect.short_unmanaged": "keine Engine verwaltet es",
"migrate.gone": "Der RTE-Container des Runners in {dir} ist weg, daher können Name und Token seines Runners nicht gelesen werden.\nStarten Sie ihn wieder, oder installieren Sie einen neuen Runner und geben Sie ihn in AnsibleForms frei.",
Expand Down
2 changes: 2 additions & 0 deletions internal/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"detect.why_runnerLayout": "This Runner was not set up by the installer.\nUpgrade it by hand, or install a new Runner with the installer and approve it in AnsibleForms.",
"detect.why_nativeLayout": "Its folder is not the installer's layout (releases/, current, app.env and rte.env).\nUpgrade it by hand, the way it was installed.",
"detect.why_noHelm": "It runs on Kubernetes, but not as a release of the ansibleforms Helm chart (or helm is missing here).\nUpgrade it with the tool that deployed it.",
"detect.why_argoCD": "It runs on Kubernetes, managed by the Argo CD Application {app}.\nThe installer leaves it alone: upgrade it in Argo CD, by changing the chart version of the Application.",
"detect.why_unknownVersion": "Its version could not be read.\nStart it so it answers on its port, then run the installer again.",
"detect.why_unmanaged": "AnsibleForms {version} answers on port {port}, but no engine manages it (run from source or by hand).\nThe installer cannot upgrade it. A new installation could clash with its ports or its database.",
"detect.why_unmanagedRunner": "An AnsibleForms RTE answers on port {port}, but no engine manages it (run from source or by hand).\nThe installer cannot upgrade it. A new installation could clash with its port.",
Expand All @@ -322,6 +323,7 @@
"detect.short_runnerLayout": "this Runner was not set up by the installer",
"detect.short_nativeLayout": "its folder is not in the installer's layout",
"detect.short_noHelm": "not a release of the ansibleforms Helm chart",
"detect.short_argoCD": "managed by Argo CD (Application {app})",
"detect.short_unknownVersion": "its version cannot be read",
"detect.short_unmanaged": "no engine manages it",
"migrate.gone": "The RTE container of the Runner in {dir} is gone, so its runner's name and token cannot be read.\nStart it again, or install a new Runner and approve it in AnsibleForms.",
Expand Down
2 changes: 2 additions & 0 deletions internal/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"detect.why_runnerLayout": "Este Runner no lo configuró el instalador.\nActualícelo a mano, o instale un nuevo Runner con el instalador y apruébelo en AnsibleForms.",
"detect.why_nativeLayout": "Su carpeta no sigue la estructura del instalador (releases/, current, app.env y rte.env).\nActualícelo a mano, del mismo modo en que se instaló.",
"detect.why_noHelm": "Se ejecuta en Kubernetes, pero no como release del chart Helm ansibleforms (o falta helm aquí).\nActualícelo con la herramienta que lo desplegó.",
"detect.why_argoCD": "Se ejecuta en Kubernetes, gestionado por la Application de Argo CD {app}.\nEl instalador no lo toca: actualízalo en Argo CD, cambiando la versión del chart de la Application.",
"detect.why_unknownVersion": "No se pudo leer su versión.\nInícielo para que responda en su puerto y vuelva a ejecutar el instalador.",
"detect.why_unmanaged": "AnsibleForms {version} responde en el puerto {port}, pero ningún motor lo gestiona (ejecutado desde el código o a mano).\nEl instalador no puede actualizarlo. Una nueva instalación podría chocar con sus puertos o su base de datos.",
"detect.why_unmanagedRunner": "Un RTE de AnsibleForms responde en el puerto {port}, pero ningún motor lo gestiona (ejecutado desde el código o a mano).\nEl instalador no puede actualizarlo. Una nueva instalación podría chocar con su puerto.",
Expand All @@ -322,6 +323,7 @@
"detect.short_runnerLayout": "este Runner no lo instaló el instalador",
"detect.short_nativeLayout": "su carpeta no sigue la estructura del instalador",
"detect.short_noHelm": "no es una release del chart Helm de ansibleforms",
"detect.short_argoCD": "gestionado por Argo CD (Application {app})",
"detect.short_unknownVersion": "no se puede leer su versión",
"detect.short_unmanaged": "ningún motor lo gestiona",
"migrate.gone": "El contenedor RTE del Runner de {dir} ya no existe, así que no se pueden leer el nombre ni el token de su runner.\nVuelve a iniciarlo, o instala un Runner nuevo y apruébalo en AnsibleForms.",
Expand Down
2 changes: 2 additions & 0 deletions internal/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"detect.why_runnerLayout": "Ce Runner n'a pas été mis en place par l'installateur.\nMettez-le à jour à la main, ou installez un nouveau Runner avec l'installateur et approuvez-le dans AnsibleForms.",
"detect.why_nativeLayout": "Son dossier ne suit pas la structure de l'installateur (releases/, current, app.env et rte.env).\nMettez-le à jour à la main, comme il a été installé.",
"detect.why_noHelm": "Il tourne sur Kubernetes, mais pas comme release du chart Helm ansibleforms (ou helm manque ici).\nMettez-le à jour avec l'outil qui l'a déployé.",
"detect.why_argoCD": "Il tourne sur Kubernetes, géré par l'Application Argo CD {app}.\nL'installateur n'y touche pas : mets-le à jour dans Argo CD, en changeant la version du chart de l'Application.",
"detect.why_unknownVersion": "Sa version n'a pas pu être lue.\nDémarrez-le pour qu'il réponde sur son port, puis relancez l'installateur.",
"detect.why_unmanaged": "AnsibleForms {version} répond sur le port {port}, mais aucun moteur ne le gère (lancé depuis les sources ou à la main).\nL'installateur ne peut pas le mettre à jour. Une nouvelle installation pourrait entrer en conflit avec ses ports ou sa base.",
"detect.why_unmanagedRunner": "Un RTE AnsibleForms répond sur le port {port}, mais aucun moteur ne le gère (lancé depuis les sources ou à la main).\nL'installateur ne peut pas le mettre à jour. Une nouvelle installation pourrait entrer en conflit avec son port.",
Expand All @@ -322,6 +323,7 @@
"detect.short_runnerLayout": "ce Runner n'a pas été installé par l'installateur",
"detect.short_nativeLayout": "son dossier ne suit pas l'organisation de l'installateur",
"detect.short_noHelm": "pas une release du chart Helm ansibleforms",
"detect.short_argoCD": "géré par Argo CD (Application {app})",
"detect.short_unknownVersion": "sa version est illisible",
"detect.short_unmanaged": "aucun moteur ne le gère",
"migrate.gone": "Le conteneur RTE du Runner dans {dir} n'existe plus : le nom et le jeton de son runner ne peuvent pas être lus.\nRedémarrez-le, ou installez un nouveau Runner et approuvez-le dans AnsibleForms.",
Expand Down
2 changes: 2 additions & 0 deletions internal/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
"detect.why_runnerLayout": "Questo Runner non è stato configurato dall'installer.\nAggiornalo a mano, oppure installa un nuovo Runner con l'installer e approvalo in AnsibleForms.",
"detect.why_nativeLayout": "La sua cartella non segue la struttura dell'installer (releases/, current, app.env e rte.env).\nAggiornalo a mano, come è stato installato.",
"detect.why_noHelm": "Gira su Kubernetes, ma non come release del chart Helm ansibleforms (o qui manca helm).\nAggiornalo con lo strumento che lo ha distribuito.",
"detect.why_argoCD": "Gira su Kubernetes, gestito dall'Application di Argo CD {app}.\nL'installer non lo tocca: aggiornalo in Argo CD, cambiando la versione del chart dell'Application.",
"detect.why_unknownVersion": "Non è stato possibile leggere la sua versione.\nAvvialo perché risponda sulla sua porta, poi esegui di nuovo l'installer.",
"detect.why_unmanaged": "AnsibleForms {version} risponde sulla porta {port}, ma nessun motore lo gestisce (avviato dai sorgenti o a mano).\nL'installer non può aggiornarlo. Una nuova installazione potrebbe entrare in conflitto con le sue porte o il suo database.",
"detect.why_unmanagedRunner": "Un RTE di AnsibleForms risponde sulla porta {port}, ma nessun motore lo gestisce (avviato dai sorgenti o a mano).\nL'installer non può aggiornarlo. Una nuova installazione potrebbe entrare in conflitto con la sua porta.",
Expand All @@ -322,6 +323,7 @@
"detect.short_runnerLayout": "questo Runner non è stato installato dall'installer",
"detect.short_nativeLayout": "la sua cartella non segue la struttura dell'installer",
"detect.short_noHelm": "non è una release del chart Helm di ansibleforms",
"detect.short_argoCD": "gestito da Argo CD (Application {app})",
"detect.short_unknownVersion": "la sua versione non è leggibile",
"detect.short_unmanaged": "nessun motore lo gestisce",
"migrate.gone": "Il container RTE del Runner in {dir} non c'è più, quindi nome e token del suo runner non si possono leggere.\nAvvialo di nuovo, oppure installa un nuovo Runner e approvalo in AnsibleForms.",
Expand Down
Loading
Loading