diff --git a/internal/cli/install.go b/internal/cli/install.go index af8478b..c0ecd45 100644 --- a/internal/cli/install.go +++ b/internal/cli/install.go @@ -185,7 +185,10 @@ func install(ctx context.Context, f Flags) error { controller := func() bool { return p.role == compose.RoleController } runner := func() bool { return p.role == compose.RoleRunner } - kubernetes := func() bool { return controller() && p.mode == string(state.Kubernetes) } + // on Kubernetes : a controller (the whole chart) or a Runner (the chart's runner mode) + onKube := func() bool { return p.mode == string(state.Kubernetes) } + kubernetes := func() bool { return controller() && onKube() } + kubeRunner := func() bool { return runner() && onKube() } composeCtl := func() bool { return controller() && !kubernetes() } nativeMode := func() bool { return p.mode == string(state.Native) } @@ -257,7 +260,7 @@ func install(ctx context.Context, f Flags) error { return nil }}, // compose : the folder and the instance - {label: "folder", skip: kubernetes, ask: func() error { + {label: "folder", skip: onKube, ask: func() error { def := "/opt/ansibleforms" if runner() { def = "/opt/ansibleforms-runner" @@ -278,7 +281,7 @@ func install(ctx context.Context, f Flags) error { p.dir, _ = filepath.Abs(d) return nil }}, - {label: "folder", skip: kubernetes, ask: func() error { + {label: "folder", skip: onKube, ask: func() error { def := map[bool]string{true: "runner", false: "af"}[runner()] help, title := i18n.T("install.nameHelp"), i18n.T("install.name") if nativeMode() { @@ -298,7 +301,8 @@ func install(ctx context.Context, f Flags) error { {label: "app", skip: func() bool { return !runner() }, ask: func() error { return askApp(ctx, f, p) }}, - {label: "mode", skip: func() bool { return !runner() || f.RunnerMode != "" }, ask: func() error { + // on Kubernetes a Runner dials the app (outbound) : nothing reaches it from outside + {label: "mode", skip: func() bool { return !runner() || f.RunnerMode != "" || kubeRunner() }, ask: func() error { ui.Explain(i18n.T("runner.modeHelp")) m, err := ui.Select(i18n.T("runner.mode"), []ui.Option{ {Label: i18n.T("runner.modeOutbound"), Value: "outbound", Hint: i18n.T("runner.modeOutboundHint")}, @@ -311,7 +315,7 @@ func install(ctx context.Context, f Flags) error { return err }}, // the network : the controller's web port, a runner's port (and address, inbound) - {label: "network", skip: func() bool { return kubernetes() || f.Port != 0 }, ask: func() error { + {label: "network", skip: func() bool { return onKube() || f.Port != 0 }, ask: func() error { title, def := i18n.T("install.port"), compose.FreePort("0.0.0.0", 443) ui.Explain(i18n.T("install.portHelp")) if runner() { @@ -328,7 +332,7 @@ func install(ctx context.Context, f Flags) error { p.port, _ = strconv.Atoi(s) return nil }}, - {label: "network", skip: func() bool { return !runner() || p.runnerMode != "inbound" }, ask: func() error { + {label: "network", skip: func() bool { return !runner() || p.runnerMode != "inbound" || kubeRunner() }, ask: func() error { def := orStr(p.host, orStr(f.Host, compose.OutboundIP())) ui.Explain(i18n.T("runner.addressHelp")) h, err := textAgain(f.Host, i18n.T("runner.address"), def, notEmpty) @@ -378,14 +382,19 @@ func install(ctx context.Context, f Flags) error { } return nil }}, - // Kubernetes : the release, its ingress, its volumes - {label: "cluster", skip: func() bool { return !kubernetes() }, ask: func() error { + // Kubernetes : the release, its ingress (a controller), its volumes + {label: "cluster", skip: func() bool { return !onKube() }, ask: func() error { ns, err := textAgain(f.Namespace, i18n.T("kube.namespace"), orStr(p.namespace, "ansibleforms"), notEmpty) p.namespace = ns return err }}, - {label: "cluster", skip: func() bool { return !kubernetes() }, ask: func() error { - r, err := textAgain(f.Release, i18n.T("kube.release"), orStr(p.release, "ansibleforms"), notEmpty) + {label: "cluster", skip: func() bool { return !onKube() }, ask: func() error { + // a Runner's release is also its runner's name under Connections > Runners + def := map[bool]string{true: "ansibleforms-runner", false: "ansibleforms"}[runner()] + if runner() { + ui.Explain(i18n.T("kube.releaseRunnerHelp")) + } + r, err := textAgain(f.Release, i18n.T("kube.release"), orStr(p.release, def), notEmpty) p.release = r if err == nil { ui.Answered(p.namespace + " / " + r) @@ -410,7 +419,7 @@ func install(ctx context.Context, f Flags) error { } return err }}, - {label: "storage", skip: func() bool { return !kubernetes() }, ask: func() error { + {label: "storage", skip: func() bool { return !onKube() }, ask: func() error { s, err := choice(f.StorageClass, i18n.T("kube.storageClass"), append([]string{""}, kube.StorageClasses(ctx)...), p.storageClass) p.storageClass = s return err @@ -441,6 +450,8 @@ func install(ctx context.Context, f Flags) error { switch { case kubernetes(): return doKube(ctx, f, p) + case kubeRunner(): + return doKubeRunner(ctx, f, p) case runner(): return doRunner(ctx, p) } @@ -502,6 +513,15 @@ func review(p *plan) []string { } out := []string{row("version", p.version.String())} switch { + case p.role == compose.RoleRunner && p.mode == string(state.Kubernetes): + out = append(out, row("role", i18n.T("install.roleRunnerShort")), row("engine", i18n.T("install.modeKubernetesShort")), + row("cluster", p.namespace+" / "+p.release), row("app", p.appURL), row("mode", i18n.T("runner.mode_outbound")), + row("storage", orStr(p.storageClass, i18n.T("kube.default")))) + if p.join != "" { + out = append(out, row("register", i18n.T("runner.registerJoinShort"))) + } else { + out = append(out, row("register", i18n.T("runner.registerApproveShort"))) + } case p.role == compose.RoleRunner: out = append(out, row("role", i18n.T("install.roleRunnerShort")), row("engine", engine), row("folder", p.dir), row("name", p.name), row("app", p.appURL), row("mode", i18n.T("runner.mode_"+p.runnerMode))) @@ -744,6 +764,49 @@ func doKube(ctx context.Context, f Flags, p *plan) error { return nil } +// doKubeRunner installs a Runner on Kubernetes : the chart's runner mode, an RTE alone that +// dials the app (outbound), approved at once with a join token, else by an admin. +// +// Args: +// +// ctx: the context, to cancel. +// f: the flags. +// p: the answers. +// +// Errors: +// +// When the join token is not valid or helm fails. +func doKubeRunner(ctx context.Context, f Flags, p *plan) error { + // its token made here, so its fingerprint is said for the admin who approves it + r := &kube.RunnerOptions{AppURL: p.appURL, Name: p.release, TrustAppCert: p.trust, Token: compose.Secret(32)} + if p.join != "" { + token, err := compose.ParseJoin(p.join) + if err != nil { + return err + } + r.JoinToken = token + } + st, err := kube.Install(ctx, kube.Options{Version: p.version.String(), Namespace: p.namespace, Release: p.release, + StorageClass: p.storageClass, ExtraValues: f.Values, Runner: r}) + if err != nil { + return err + } + lines := []string{ + i18n.T("runner.doneName", "name", p.release), + i18n.T("runner.doneMode", "mode", i18n.T("runner.mode_outbound")), + i18n.T("runner.doneApp", "url", r.AppURL), + "", + } + if r.JoinToken != "" { + lines = append(lines, i18n.T("runner.doneRegistered")) + } else { + lines = append(lines, i18n.T("runner.doneApprove"), "", i18n.T("runner.doneFingerprint", "fingerprint", compose.Fingerprint(r.Token))) + } + lines = append(lines, "", i18n.T("kube.doneValues", "file", st.ValuesFile)) + ui.Summary(i18n.T("runner.done", "version", st.Version), lines) + return nil +} + // ─── container engines ──────────────────────────────────────────────────────── // chooseEngine picks Docker or Podman, or Kubernetes for a controller, in one list : the @@ -757,7 +820,7 @@ func doKube(ctx context.Context, f Flags, p *plan) error { // tools: what the server has. // prev: the answer of before (Esc), the default. // withKube: Kubernetes is listed (no --mode). -// runner: a runner, for which Kubernetes is always greyed out (the Helm chart installs it). +// runner: a runner : Kubernetes installs the chart's runner mode, an RTE alone. // // Returns: // @@ -787,7 +850,7 @@ func chooseEngine(ctx context.Context, f Flags, tools system.Tools, prev string, } opts = append(opts, ui.Option{Label: i18n.T("runtime.toInstall", "runtime", name), Value: string(r), Hint: i18n.T("runtime." + string(r) + "InstallHint")}) } - kube := withKube && !runner && tools.Kubectl && tools.Helm && tools.Cluster + kube := withKube && tools.Kubectl && tools.Helm && tools.Cluster nativeOption := func() ui.Option { hint := "install.modeNativeHint" if runner { @@ -800,8 +863,8 @@ func chooseEngine(ctx context.Context, f Flags, tools system.Tools, prev string, } if withKube { switch { - case runner: - opts = append(opts, ui.Option{Label: i18n.T("install.modeKubernetesRunner"), Value: string(state.Kubernetes), Disabled: true}) + case kube && runner: + opts = append(opts, ui.Option{Label: i18n.T("install.modeKubernetes"), Value: string(state.Kubernetes), Hint: i18n.T("install.modeKubernetesRunnerHint")}) case kube: opts = append(opts, ui.Option{Label: i18n.T("install.modeKubernetes"), Value: string(state.Kubernetes), Hint: i18n.T("install.modeKubernetesHint")}) default: diff --git a/internal/i18n/locales/ca.json b/internal/i18n/locales/ca.json index b8aba24..9fb703e 100644 --- a/internal/i18n/locales/ca.json +++ b/internal/i18n/locales/ca.json @@ -103,6 +103,7 @@ "join.invalid": "Aquest no és un token d'unió vàlid.", "kube.namespace": "Namespace", "kube.release": "Nom de la release de Helm", + "kube.releaseRunnerHelp": "El nom del seu runner a Connexions > Runners.", "kube.hostname": "Nom d'amfitrió servit per l'ingress (ansibleforms.example.com)", "kube.ingressClass": "Classe d'ingress", "kube.tls": "Voleu servir-lo per HTTPS (amb el certificat de l'ingress)?", @@ -247,7 +248,7 @@ "keys.quit": "surt", "keys.stop": "atura", "keys.close": "tanca", - "install.modeKubernetesRunner": "Kubernetes (allà, un Runner s'instal·la amb el chart de Helm)", + "install.modeKubernetesRunnerHint": "El mode runner del chart de Helm, al clúster que kubectl abasta.\nTruca a l'app: res no hi arriba des de fora.", "install.modeNative": "Directament en aquest servidor, sense contenidors", "install.modeNative_root": "Directament en aquest servidor (cal root: executeu-lo amb sudo)", "install.modeNative_unsupported": "Directament en aquest servidor (no compatible amb aquest Linux)", diff --git a/internal/i18n/locales/de.json b/internal/i18n/locales/de.json index 86c1f7d..2639a79 100644 --- a/internal/i18n/locales/de.json +++ b/internal/i18n/locales/de.json @@ -103,6 +103,7 @@ "join.invalid": "Das ist kein gültiges Beitritts-Token.", "kube.namespace": "Namespace", "kube.release": "Name des Helm-Release", + "kube.releaseRunnerHelp": "Der Name seines Runners unter Verbindungen > Runner.", "kube.hostname": "Vom Ingress bereitgestellter Hostname (ansibleforms.example.com)", "kube.ingressClass": "Ingress-Klasse", "kube.tls": "Über HTTPS bereitstellen (mit dem Zertifikat des Ingress)?", @@ -247,7 +248,7 @@ "keys.quit": "beenden", "keys.stop": "stoppen", "keys.close": "schließen", - "install.modeKubernetesRunner": "Kubernetes (ein Runner dort wird mit dem Helm-Chart installiert)", + "install.modeKubernetesRunnerHint": "Der Runner-Modus des Helm-Charts, auf dem Cluster, den kubectl erreicht.\nEr ruft die App an: nichts erreicht ihn von außen.", "install.modeNative": "Direkt auf diesem Server, ohne Container", "install.modeNative_root": "Direkt auf diesem Server (erfordert root: Installer mit sudo starten)", "install.modeNative_unsupported": "Direkt auf diesem Server (auf diesem Linux nicht unterstützt)", diff --git a/internal/i18n/locales/en.json b/internal/i18n/locales/en.json index d16cafd..12c14bd 100644 --- a/internal/i18n/locales/en.json +++ b/internal/i18n/locales/en.json @@ -103,6 +103,7 @@ "join.invalid": "This is not a valid join token.", "kube.namespace": "Namespace", "kube.release": "Helm release name", + "kube.releaseRunnerHelp": "Its runner's name under Connections > Runners.", "kube.hostname": "Hostname served by the ingress (ansibleforms.example.com)", "kube.ingressClass": "Ingress class", "kube.tls": "Serve it over HTTPS (using the ingress certificate)?", @@ -247,7 +248,7 @@ "keys.quit": "quit", "keys.stop": "stop", "keys.close": "close", - "install.modeKubernetesRunner": "Kubernetes (a Runner there is installed with the Helm chart)", + "install.modeKubernetesRunnerHint": "The Helm chart's runner mode, on the cluster that kubectl reaches.\nIt dials the app: nothing reaches it from outside.", "install.modeNative": "Directly on this server, no containers", "install.modeNative_root": "Directly on this server (needs root: run the installer with sudo)", "install.modeNative_unsupported": "Directly on this server (not supported on this Linux)", diff --git a/internal/i18n/locales/es.json b/internal/i18n/locales/es.json index b7596ee..a8bc5e2 100644 --- a/internal/i18n/locales/es.json +++ b/internal/i18n/locales/es.json @@ -103,6 +103,7 @@ "join.invalid": "Esto no es un token de unión válido.", "kube.namespace": "Namespace", "kube.release": "Nombre de la release de Helm", + "kube.releaseRunnerHelp": "El nombre de su runner en Conexiones > Runners.", "kube.hostname": "Nombre de host servido por el ingress (ansibleforms.example.com)", "kube.ingressClass": "Clase de ingress", "kube.tls": "¿Servirlo por HTTPS (con el certificado del ingress)?", @@ -247,7 +248,7 @@ "keys.quit": "salir", "keys.stop": "detener", "keys.close": "cerrar", - "install.modeKubernetesRunner": "Kubernetes (allí, un Runner se instala con el chart de Helm)", + "install.modeKubernetesRunnerHint": "El modo runner del chart de Helm, en el clúster que alcanza kubectl.\nLlama a la app: nada le llega desde fuera.", "install.modeNative": "Directamente en este servidor, sin contenedores", "install.modeNative_root": "Directamente en este servidor (requiere root: ejecútalo con sudo)", "install.modeNative_unsupported": "Directamente en este servidor (no compatible con este Linux)", diff --git a/internal/i18n/locales/fr.json b/internal/i18n/locales/fr.json index f0b1e2f..40cf25c 100644 --- a/internal/i18n/locales/fr.json +++ b/internal/i18n/locales/fr.json @@ -103,6 +103,7 @@ "join.invalid": "Ce n'est pas un jeton d'adhésion valide.", "kube.namespace": "Namespace", "kube.release": "Nom de la release Helm", + "kube.releaseRunnerHelp": "Le nom de son runner dans Connexions > Runners.", "kube.hostname": "Nom d'hôte servi par l'ingress (ansibleforms.example.com)", "kube.ingressClass": "Classe d'ingress", "kube.tls": "Le servir en HTTPS (avec le certificat de l'ingress) ?", @@ -247,7 +248,7 @@ "keys.quit": "quitter", "keys.stop": "arrêter", "keys.close": "fermer", - "install.modeKubernetesRunner": "Kubernetes (un Runner s'y installe avec le chart Helm)", + "install.modeKubernetesRunnerHint": "Le mode runner du chart Helm, sur le cluster que kubectl atteint.\nIl appelle l'app : rien ne l'atteint de l'extérieur.", "install.modeNative": "Directement sur ce serveur, sans conteneurs", "install.modeNative_root": "Directement sur ce serveur (nécessite root : lancez-le avec sudo)", "install.modeNative_unsupported": "Directement sur ce serveur (non pris en charge sur ce Linux)", diff --git a/internal/i18n/locales/it.json b/internal/i18n/locales/it.json index 0e591f3..21d9733 100644 --- a/internal/i18n/locales/it.json +++ b/internal/i18n/locales/it.json @@ -103,6 +103,7 @@ "join.invalid": "Questo non è un token di adesione valido.", "kube.namespace": "Namespace", "kube.release": "Nome della release Helm", + "kube.releaseRunnerHelp": "Il nome del suo runner in Connessioni > Runner.", "kube.hostname": "Hostname servito dall'ingress (ansibleforms.example.com)", "kube.ingressClass": "Classe di ingress", "kube.tls": "Servirlo in HTTPS (con il certificato dell'ingress)?", @@ -247,7 +248,7 @@ "keys.quit": "esci", "keys.stop": "ferma", "keys.close": "chiudi", - "install.modeKubernetesRunner": "Kubernetes (lì un Runner si installa con il chart Helm)", + "install.modeKubernetesRunnerHint": "La modalità runner del chart Helm, sul cluster che kubectl raggiunge.\nChiama l'app: niente lo raggiunge dall'esterno.", "install.modeNative": "Direttamente su questo server, senza container", "install.modeNative_root": "Direttamente su questo server (richiede root: eseguilo con sudo)", "install.modeNative_unsupported": "Direttamente su questo server (non supportato su questo Linux)", diff --git a/internal/i18n/locales/ja.json b/internal/i18n/locales/ja.json index b273073..0e85967 100644 --- a/internal/i18n/locales/ja.json +++ b/internal/i18n/locales/ja.json @@ -103,6 +103,7 @@ "join.invalid": "有効な参加トークンではありません。", "kube.namespace": "Namespace", "kube.release": "Helm リリース名", + "kube.releaseRunnerHelp": "接続 > Runner に表示される runner の名前です。", "kube.hostname": "Ingress で公開するホスト名 (ansibleforms.example.com)", "kube.ingressClass": "Ingress クラス", "kube.tls": "HTTPS で公開しますか (Ingress の証明書を使用)?", @@ -247,7 +248,7 @@ "keys.quit": "終了", "keys.stop": "中止", "keys.close": "閉じる", - "install.modeKubernetesRunner": "Kubernetes (ランナーは Helm チャートでインストールします)", + "install.modeKubernetesRunnerHint": "kubectl が届くクラスター上の Helm chart の runner モード。\nアプリに接続します。外部から接続されることはありません。", "install.modeNative": "このサーバーに直接 (コンテナーなし)", "install.modeNative_root": "このサーバーに直接 (root が必要: sudo で実行)", "install.modeNative_unsupported": "このサーバーに直接 (この Linux では非対応)", diff --git a/internal/i18n/locales/nl.json b/internal/i18n/locales/nl.json index d9dc49b..42e0aad 100644 --- a/internal/i18n/locales/nl.json +++ b/internal/i18n/locales/nl.json @@ -103,6 +103,7 @@ "join.invalid": "Dit is geen geldig jointoken.", "kube.namespace": "Namespace", "kube.release": "Naam van de Helm-release", + "kube.releaseRunnerHelp": "De naam van zijn runner onder Verbindingen > Runners.", "kube.hostname": "Hostnaam van de ingress (ansibleforms.example.com)", "kube.ingressClass": "Ingress-klasse", "kube.tls": "Aanbieden via HTTPS (met het certificaat van de ingress)?", @@ -247,7 +248,7 @@ "keys.quit": "afsluiten", "keys.stop": "stoppen", "keys.close": "sluiten", - "install.modeKubernetesRunner": "Kubernetes (daar installeert u een Runner met de Helm-chart)", + "install.modeKubernetesRunnerHint": "De runnermodus van de Helm-chart, op het cluster dat kubectl bereikt.\nHij belt de app: niets bereikt hem van buitenaf.", "install.modeNative": "Direct op deze server, zonder containers", "install.modeNative_root": "Direct op deze server (vereist root: start het met sudo)", "install.modeNative_unsupported": "Direct op deze server (niet ondersteund op deze Linux)", diff --git a/internal/i18n/locales/pl.json b/internal/i18n/locales/pl.json index 278d014..313d42a 100644 --- a/internal/i18n/locales/pl.json +++ b/internal/i18n/locales/pl.json @@ -103,6 +103,7 @@ "join.invalid": "To nie jest prawidłowy token dołączenia.", "kube.namespace": "Namespace", "kube.release": "Nazwa wydania Helm", + "kube.releaseRunnerHelp": "Nazwa jego runnera w Połączenia > Runnery.", "kube.hostname": "Nazwa hosta obsługiwana przez ingress (ansibleforms.example.com)", "kube.ingressClass": "Klasa ingress", "kube.tls": "Udostępniać przez HTTPS (z certyfikatem ingressu)?", @@ -247,7 +248,7 @@ "keys.quit": "zakończ", "keys.stop": "zatrzymaj", "keys.close": "zamknij", - "install.modeKubernetesRunner": "Kubernetes (tam Runner instaluje się chartem Helm)", + "install.modeKubernetesRunnerHint": "Tryb runnera chartu Helm, w klastrze, do którego dociera kubectl.\nŁączy się z aplikacją: nic nie łączy się z nim z zewnątrz.", "install.modeNative": "Bezpośrednio na tym serwerze, bez kontenerów", "install.modeNative_root": "Bezpośrednio na tym serwerze (wymaga root: uruchom przez sudo)", "install.modeNative_unsupported": "Bezpośrednio na tym serwerze (nieobsługiwane w tym Linuksie)", diff --git a/internal/i18n/locales/pt.json b/internal/i18n/locales/pt.json index 22f0897..b5b7187 100644 --- a/internal/i18n/locales/pt.json +++ b/internal/i18n/locales/pt.json @@ -103,6 +103,7 @@ "join.invalid": "Isto não é um token de adesão válido.", "kube.namespace": "Namespace", "kube.release": "Nome da release Helm", + "kube.releaseRunnerHelp": "O nome do seu runner em Ligações > Runners.", "kube.hostname": "Nome de anfitrião servido pelo ingress (ansibleforms.example.com)", "kube.ingressClass": "Classe de ingress", "kube.tls": "Servir por HTTPS (com o certificado do ingress)?", @@ -247,7 +248,7 @@ "keys.quit": "sair", "keys.stop": "parar", "keys.close": "fechar", - "install.modeKubernetesRunner": "Kubernetes (aí, um Runner instala-se com o chart Helm)", + "install.modeKubernetesRunnerHint": "O modo runner do chart Helm, no cluster que o kubectl alcança.\nLiga à app: nada lhe chega de fora.", "install.modeNative": "Diretamente neste servidor, sem contentores", "install.modeNative_root": "Diretamente neste servidor (requer root: execute o instalador com sudo)", "install.modeNative_unsupported": "Diretamente neste servidor (não suportado neste Linux)", diff --git a/internal/i18n/locales/zh.json b/internal/i18n/locales/zh.json index 632e8fc..2545af4 100644 --- a/internal/i18n/locales/zh.json +++ b/internal/i18n/locales/zh.json @@ -103,6 +103,7 @@ "join.invalid": "这不是有效的加入令牌。", "kube.namespace": "Namespace", "kube.release": "Helm release 名称", + "kube.releaseRunnerHelp": "它在 连接 > Runner 中的名称。", "kube.hostname": "Ingress 提供的主机名(ansibleforms.example.com)", "kube.ingressClass": "Ingress 类", "kube.tls": "通过 HTTPS 提供服务(使用 Ingress 证书)?", @@ -247,7 +248,7 @@ "keys.quit": "退出", "keys.stop": "停止", "keys.close": "关闭", - "install.modeKubernetesRunner": "Kubernetes(运行器需使用 Helm chart 安装)", + "install.modeKubernetesRunnerHint": "Helm chart 的 runner 模式,部署在 kubectl 可访问的集群上。\n它主动连接应用:外部无法连入。", "install.modeNative": "直接安装在此服务器上,不使用容器", "install.modeNative_root": "直接安装在此服务器上(需要 root:请用 sudo 运行安装程序)", "install.modeNative_unsupported": "直接安装在此服务器上(此 Linux 不受支持)", diff --git a/internal/kube/kube.go b/internal/kube/kube.go index f3d9a30..bbcb972 100644 --- a/internal/kube/kube.go +++ b/internal/kube/kube.go @@ -38,6 +38,22 @@ type Options struct { // ExtraValues is a values file of the person's own, applied after the installer's // (an external database, resources, node selectors). ExtraValues string + // Runner : a Runner alone, the chart's runner mode (runner.enabled), no app nor database + Runner *RunnerOptions +} + +// RunnerOptions are the answers of a Runner on Kubernetes : an RTE alone that dials its app. +type RunnerOptions struct { + // AppURL is the app's address as people open it (https://forms.example.com). + AppURL string + // Name is its runner's name under Connections > Runners (the release's name). + Name string + // JoinToken approves it at once ; empty, it waits for an admin's approval. + JoinToken string + // Token is its RTE_TOKEN, made by the installer so its fingerprint can be said. + Token string + // TrustAppCert accepts the app's self-signed certificate (APP_TLS_VERIFY=0). + TrustAppCert bool } // IngressClasses lists the cluster's ingress classes. @@ -96,6 +112,23 @@ func values(o Options) ([]byte, error) { } return m } + if r := o.Runner; r != nil { + runner := map[string]any{"enabled": true, "appUrl": r.AppURL, "name": r.Name, "appTlsVerify": !r.TrustAppCert} + if r.JoinToken != "" { + runner["joinToken"] = r.JoinToken + } + v := map[string]any{ + "runner": runner, + "applications": map[string]any{"rte": map[string]any{"token": r.Token}}, + "storages": map[string]any{"server": storage()}, + } + head := []byte("# Chart values written by the AnsibleForms installer for a Runner; the upgrades apply them again.\n") + body, err := yaml.Marshal(v) + if err != nil { + return nil, err + } + return append(head, body...), nil + } v := map[string]any{ "secrets": map[string]any{"generate": true}, "ingress": map[string]any{ @@ -174,6 +207,10 @@ func Install(ctx context.Context, o Options) (*state.State, error) { scheme = "http" } st.URL = scheme + "://" + o.Hostname + if o.Runner != nil { + // a Runner serves nothing : its address is the app's it dials + st.Role, st.URL = "runner", o.Runner.AppURL + } data, err := values(o) if err != nil { return nil, err diff --git a/internal/kube/kube_test.go b/internal/kube/kube_test.go new file mode 100644 index 0000000..1e94055 --- /dev/null +++ b/internal/kube/kube_test.go @@ -0,0 +1,36 @@ +package kube + +import ( + "strings" + "testing" + + "gopkg.in/yaml.v3" +) + +// TestValuesRunner checks a Runner's values : the chart's runner mode with the app it dials, +// its name, its token and its join token, its volume, and nothing of the app (no ingress). +func TestValuesRunner(t *testing.T) { + data, err := values(Options{StorageClass: "fast", Runner: &RunnerOptions{ + AppURL: "https://forms.example.com", Name: "dc2-runner", JoinToken: "afj_x", Token: "t0123456789abcdef", TrustAppCert: true, + }}) + if err != nil { + t.Fatal(err) + } + var v map[string]any + if err := yaml.Unmarshal(data, &v); err != nil { + t.Fatal(err) + } + r := v["runner"].(map[string]any) + if r["enabled"] != true || r["appUrl"] != "https://forms.example.com" || r["name"] != "dc2-runner" || r["joinToken"] != "afj_x" || r["appTlsVerify"] != false { + t.Fatalf("runner values = %v", r) + } + if tok := v["applications"].(map[string]any)["rte"].(map[string]any)["token"]; tok != "t0123456789abcdef" { + t.Fatalf("rte token = %v", tok) + } + if v["storages"].(map[string]any)["server"].(map[string]any)["className"] != "fast" { + t.Fatalf("storages = %v", v["storages"]) + } + if _, ok := v["ingress"]; ok || !strings.Contains(string(data), "for a Runner") { + t.Fatalf("a Runner has no ingress: %s", data) + } +}