From f8e0515ff653055cc3c59c6f4cac4ca7eca4462f Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Wed, 9 Oct 2024 10:44:37 +0300 Subject: [PATCH 1/2] fix: agent install instructions Update FluxInstallAgent.tsx Update CLIInstallAgent.tsx Update CLIInstallAgent.unit.test.tsx Update FluxInstallAgent.unit.test.tsx Update CLIInstallAgent.tsx Update CLIInstallAgent.unit.test.tsx refactor: lift up the data composition to parent component chore: fix failing tests chore: fix issue after running npm i fix: make the values being passed to helm & flux template consistent and external fix: fix issues --- package-lock.json | 2 +- src/components/Agents/Add/AddAgentForm.tsx | 4 +- .../CLIInstallAgent.tsx | 72 ++------ .../FluxInstallAgent.tsx | 89 +++------- .../InstallAgentModal.tsx | 60 ++++++- .../__tests__/CLIInstallAgent.unit.test.tsx | 79 +++++---- .../__tests__/FluxInstallAgent.unit.test.tsx | 158 ++++-------------- .../CLIInstallAgent.unit.test.tsx.snap | 40 +++++ .../FluxInstallAgent.unit.test.tsx.snap | 65 +++++++ 9 files changed, 276 insertions(+), 293 deletions(-) create mode 100644 src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap create mode 100644 src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap diff --git a/package-lock.json b/package-lock.json index dc1473eee..450efad63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44951,4 +44951,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/components/Agents/Add/AddAgentForm.tsx b/src/components/Agents/Add/AddAgentForm.tsx index 7fde2da0b..0c599117a 100644 --- a/src/components/Agents/Add/AddAgentForm.tsx +++ b/src/components/Agents/Add/AddAgentForm.tsx @@ -107,7 +107,7 @@ export default function AgentForm({ name: agent?.name ?? "", properties: agent?.properties ?? {}, kubernetes: { - interval: "30m", + schedule: "30m", enabled: false }, pushTelemetry: { @@ -173,7 +173,7 @@ export default function AgentForm({ { label: "12h", value: "12h" }, { label: "24h", value: "24h" } ]} - name="kubernetes.interval" + name="kubernetes.schedule" label="Scrape Interval" hintPosition="top" hint="How often to perform a full reconciliation of changes (in addition to real-time changes from Kubernetes events), set higher for larger clusters." diff --git a/src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx b/src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx index b260919be..af2e7bc5b 100644 --- a/src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx +++ b/src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx @@ -1,76 +1,38 @@ -import { GeneratedAgent } from "@flanksource-ui/api/services/agents"; -import { useUser } from "@flanksource-ui/context"; import CodeBlock from "@flanksource-ui/ui/Code/CodeBlock"; import Handlebars from "handlebars"; import { useMemo } from "react"; -import { AgentFormValues } from "../Add/AddAgentForm"; -import { useAgentsBaseURL } from "./useAgentsBaseURL"; +import { TemplateContextData } from "./InstallAgentModal"; -const helmCommand = `helm repo add flanksource https://flanksource.github.io/charts +// This a Handlebars template for the Helm command to install the agent and the +// kubernetes agent if the user has enabled it. +const helmCommand = `helm repo add {{ chart }} {{ repoName }}/{{ chart }} helm repo update -helm install mc-agent flanksource/mission-control-agent -n "mission-control-agent" \\ - --set upstream.createSecret=true \\ - --set upstream.host={{baseUrl}} \\ - --set upstream.username=token \\ - --set upstream.password={{generatedAgent.access_token}} \\ - --set upstream.agentName={{agentFormValues.name}} \\ -{{#if pushTelemetry}} - --set pushTelemetry.enabled=true \\ - --set pushTelemetry.topologyName={{pushTelemetry.topologyName}} -{{/if}} +helm install mc-agent flanksource/mission-control-agent -n "{{{ namespace }}}" \\ + {{#each values}} + --set {{{ this }}} \\ + {{/each}} --create-namespace -{{#if kubeOptions}} -helm install mc-agent-kubernetes flanksource/mission-control-kubernetes -n "mission-control-agent" \\ - --set scraper.clusterName="{{agentFormValues.name}}" \\ - --set scraper.interval="{{kubeOptions.interval}}" +{{#if kubeValues }} +helm install mc-agent-kubernetes flanksource/mission-control-kubernetes -n "{{{ namespace }}}" \\ + {{#each kubeValues}} + --set {{{ this }}} \\ + {{/each}} {{/if}} `; const template = Handlebars.compile(helmCommand); type Props = { - generatedAgent: GeneratedAgent; - agentFormValues?: AgentFormValues; + data: TemplateContextData; }; -export default function CLIInstallAgent({ - generatedAgent, - agentFormValues -}: Props) { - const baseUrl = useAgentsBaseURL(); - const { backendUrl, orgSlug } = useUser(); - +export default function CLIInstallAgent({ data }: Props) { const helmCommandTemplate = useMemo(() => { - const kubeOptions = agentFormValues?.kubernetes; - const pushTelemetry = agentFormValues?.pushTelemetry; - - return template( - { - generatedAgent, - baseUrl, - agentFormValues, - pushTelemetry: pushTelemetry?.enabled - ? { - ...pushTelemetry, - topologyName: orgSlug - ? `${orgSlug}-${pushTelemetry.topologyName}` - : pushTelemetry.topologyName - } - : undefined, - backendUrl, - kubeOptions: kubeOptions - ? { - interval: kubeOptions?.interval, - exclusions: `{${kubeOptions?.exclusions?.join(",")}}` - } - : undefined - }, - {} - ); - }, [agentFormValues, backendUrl, baseUrl, generatedAgent, orgSlug]); + return template(data, {}); + }, [data]); return (
diff --git a/src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx b/src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx index 944ede344..762e453ae 100644 --- a/src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx +++ b/src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx @@ -1,52 +1,46 @@ -import { GeneratedAgent } from "@flanksource-ui/api/services/agents"; -import { useUser } from "@flanksource-ui/context"; import { JSONViewer } from "@flanksource-ui/ui/Code/JSONViewer"; import Handlebars from "handlebars"; import { useMemo } from "react"; -import { AgentFormValues } from "../Add/AddAgentForm"; -import { useAgentsBaseURL } from "./useAgentsBaseURL"; +import { TemplateContextData } from "./InstallAgentModal"; +// This a Handlebars template for the HelmRelease to install the agent and the +// kubernetes agent if the user has enabled it. const fluxTemplate = `apiVersion: v1 kind: Namespace metadata: - name: mission-control-agent + name: {{ namespace }} --- +{{#if createRepository}} apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: HelmRepository metadata: - name: flanksource - namespace: mission-control-agent + name: {{ repoName }} + namespace: {{ namespace }} spec: interval: 5m0s url: https://flanksource.github.io/charts --- +{{/if}} apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata: - name: mission-control - namespace: mission-control-agent + name: {{{ chart }}} + namespace: {{ namespace }} spec: chart: spec: - chart: mission-control-agent + chart: {{{ chart }}} sourceRef: kind: HelmRepository - name: flanksource - namespace: mission-control-agent - interval: 1m + name: {{{ repoName }}} + namespace: {{{ namespace }}} + interval: 5m0s values: - upstream: - createSecret: true - host: {{baseUrl}} - username: token - agentName: {{agentFormValues.name}} - password: {{generatedAgent.access_token}} -{{#if pushTelemetry}} - pushTelemetry: - enabled: true - topologyName: {{pushTelemetry.topologyName}} -{{/if}} -{{#if kubeOptions}} +{{#each values}} + {{{ this }}} +{{/each}} + +{{#if kubeValues}} --- apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease @@ -62,53 +56,22 @@ spec: name: flanksource namespace: mission-control-agent values: - clusterName: "{{agentFormValues.name}}" - interval: "{{kubeOptions.interval}}" + {{#each kubeValues}} + {{{ this }}} + {{/each}} {{/if}} `; const template = Handlebars.compile(fluxTemplate); type Props = { - generatedAgent: GeneratedAgent; - agentFormValues?: AgentFormValues; + data: TemplateContextData; }; -export default function FluxInstallAgent({ - generatedAgent, - agentFormValues -}: Props) { - const baseUrl = useAgentsBaseURL(); - const { backendUrl, orgSlug } = useUser(); - +export default function FluxInstallAgent({ data }: Props) { const yaml = useMemo(() => { - const kubeOptions = agentFormValues?.kubernetes; - const pushTelemetry = agentFormValues?.pushTelemetry ?? undefined; - - return template( - { - generatedAgent, - baseUrl, - agentFormValues, - pushTelemetry: pushTelemetry?.enabled - ? { - ...pushTelemetry, - topologyName: orgSlug - ? `${orgSlug}-${pushTelemetry.topologyName}` - : pushTelemetry.topologyName - } - : undefined, - backendUrl, - kubeOptions: kubeOptions - ? { - interval: kubeOptions?.interval, - exclusions: kubeOptions?.exclusions - } - : undefined - }, - {} - ); - }, [agentFormValues, backendUrl, baseUrl, generatedAgent, orgSlug]); + return template(data, {}); + }, [data]); return (
diff --git a/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx b/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx index 77e1dee82..95d5baccd 100644 --- a/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx +++ b/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { GeneratedAgent } from "../../../api/services/agents"; import { Button } from "../../../ui/Buttons/Button"; import { Modal } from "../../../ui/Modal"; @@ -6,6 +6,7 @@ import { Tab, Tabs } from "../../../ui/Tabs/Tabs"; import { AgentFormValues } from "../Add/AddAgentForm"; import CLIInstallAgent from "./CLIInstallAgent"; import FluxInstallAgent from "./FluxInstallAgent"; +import { useAgentsBaseURL } from "./useAgentsBaseURL"; export function WarningBox() { return ( @@ -83,6 +84,14 @@ export function MoreInfoBox() { ); } +export type TemplateContextData = { + namespace?: string; + chart?: string; + repoName?: string; + values?: string[]; + kubeValues?: string[]; +}; + type Props = { isOpen: boolean; onClose: () => void; @@ -97,6 +106,45 @@ export default function InstallAgentModal({ agentFormValues }: Props) { const [activeTab, setActiveTab] = useState<"cli" | "flux">("cli"); + const baseUrl = useAgentsBaseURL(); + + const data = useMemo(() => { + const kubeOptions = agentFormValues?.kubernetes; + const pushTelemetry = agentFormValues?.pushTelemetry ?? undefined; + + return { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + // You can add more values here to be passed to the template for the + // values section of the HelmRelease + values: [ + `upstream.createSecret=true`, + `upstream.host=${baseUrl}`, + `upstream.username=token`, + `upstream.password=${generatedAgent?.access_token}`, + `upstream.agentName=${agentFormValues?.name}`, + ...(pushTelemetry?.enabled + ? [ + `pushTelemetry.enabled=true`, + `pushTelemetry.topologyName=${ + pushTelemetry.topologyName + }-${agentFormValues?.name}` + ] + : []) + ], + // You can add more values here to be passed to the template for the + // kubeValues section of the HelmRelease + kubeValues: kubeOptions + ? [ + `clusterName: "${agentFormValues?.name}"`, + `scraper.schedule: "${kubeOptions.schedule}"` + ] + : [] + } satisfies TemplateContextData; + }, [agentFormValues, baseUrl, generatedAgent]); + + console.log(JSON.stringify(data, null, 2)); return ( setActiveTab(v as any)} > - + - + diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx b/src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx index c90391482..7c77f9591 100644 --- a/src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx +++ b/src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx @@ -8,13 +8,41 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ disconnect: jest.fn() })); -describe("InstallAgentModal", () => { - const generatedAgent = { - id: "testid", - username: "testuser", - access_token: "testtoken" - }; +const mockInput = { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + values: [ + "upstream.createSecret=true", + "upstream.host=http://localhost:3000", + "upstream.username=token", + "upstream.password=password", + "upstream.agentName=test-new-agent-instructions", + "pushTelemetry.enabled=true", + "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" + ] +}; + +const mockInputWithKubOptions = { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + values: [ + "upstream.createSecret=true", + "upstream.host=http://localhost:3000", + "upstream.username=token", + "upstream.password=password", + "upstream.agentName=test-new-agent-instructions", + "pushTelemetry.enabled=true", + "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" + ], + kubeValues: [ + 'clusterName: "test-new-agent-instructions"', + 'scraper.schedule: "30m"' + ] +}; +describe("InstallAgentModal", () => { it("renders the Helm repository installation command", () => { render( @@ -23,26 +51,12 @@ describe("InstallAgentModal", () => { ); expect( screen.getByText( - "helm repo add flanksource https://flanksource.github.io/charts", + "helm repo add mission-control-agent flanksource/mission-control-agent", { exact: false } ).textContent - ).toMatchInlineSnapshot(` - "helm repo add flanksource https://flanksource.github.io/charts - - helm repo update - - helm install mc-agent flanksource/mission-control-agent -n "mission-control-agent" \\ - --set upstream.createSecret=true \\ - --set upstream.host=http://localhost \\ - --set upstream.username=token \\ - --set upstream.password=testtoken \\ - --set upstream.agentName= \\ - --create-namespace - - " - `); + ).toMatchSnapshot(); }); it("renders the Helm repository installation command with kube command", () => { @@ -63,28 +77,11 @@ describe("InstallAgentModal", () => { ); expect( screen.getByText( - "helm repo add flanksource https://flanksource.github.io/charts", + "helm repo add mission-control-agent flanksource/mission-control-agent", { exact: false } ).textContent - ).toMatchInlineSnapshot(` - "helm repo add flanksource https://flanksource.github.io/charts - - helm repo update - - helm install mc-agent flanksource/mission-control-agent -n "mission-control-agent" \\ - --set upstream.createSecret=true \\ - --set upstream.host=http://localhost \\ - --set upstream.username=token \\ - --set upstream.password=testtoken \\ - --set upstream.agentName=testname \\ - --create-namespace - - helm install mc-agent-kubernetes flanksource/mission-control-kubernetes -n "mission-control-agent" \\ - --set scraper.clusterName="testname" \\ - --set scraper.interval="1m" - " - `); + ).toMatchSnapshot(); }); }); diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx b/src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx index 2c4d89c88..6358b7d68 100644 --- a/src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx +++ b/src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx @@ -2,12 +2,6 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { AuthContext, FakeUser, Roles } from "../../../../context"; import FluxInstallAgent from "../FluxInstallAgent"; -const testUser = { - id: "testid", - name: "testuser", - email: "testemail" -}; - global.ResizeObserver = jest.fn().mockImplementation(() => ({ observe: jest.fn(), unobserve: jest.fn(), @@ -16,6 +10,40 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ const writeText = jest.fn(); +const mockInput = { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + values: [ + "upstream.createSecret=true", + "upstream.host=http://localhost:3000", + "upstream.username=token", + "upstream.password=password", + "upstream.agentName=test-new-agent-instructions", + "pushTelemetry.enabled=true", + "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" + ] +}; + +const mockInputWithKubOptions = { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + values: [ + "upstream.createSecret=true", + "upstream.host=http://localhost:3000", + "upstream.username=token", + "upstream.password=password", + "upstream.agentName=test-new-agent-instructions", + "pushTelemetry.enabled=true", + "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" + ], + kubeValues: [ + 'clusterName: "test-new-agent-instructions"', + 'scraper.schedule: "30m"' + ] +}; + Object.assign(navigator, { clipboard: { writeText @@ -23,12 +51,6 @@ Object.assign(navigator, { }); describe("InstallAgentModal", () => { - const generatedAgent = { - id: "testid", - username: "testuser", - access_token: "testtoken" - }; - it("renders the Helm repository installation command", async () => { render( @@ -50,61 +72,7 @@ describe("InstallAgentModal", () => { await waitFor(() => { expect(writeText).toHaveBeenCalled(); }); - expect(writeText.mock["calls"][0][0]).toMatchInlineSnapshot(` - "apiVersion: v1 - kind: Namespace - metadata: - name: mission-control-agent - --- - apiVersion: source.toolkit.fluxcd.io/v1beta1 - kind: HelmRepository - metadata: - name: flanksource - namespace: mission-control-agent - spec: - interval: 5m0s - url: https://flanksource.github.io/charts - --- - apiVersion: helm.toolkit.fluxcd.io/v2beta1 - kind: HelmRelease - metadata: - name: mission-control - namespace: mission-control-agent - spec: - chart: - spec: - chart: mission-control-agent - sourceRef: - kind: HelmRepository - name: flanksource - namespace: mission-control-agent - interval: 1m - values: - upstream: - createSecret: true - host: http://localhost - username: token - agentName: testname - password: testtoken - --- - apiVersion: helm.toolkit.fluxcd.io/v2beta1 - kind: HelmRelease - metadata: - name: mission-control-kubernetes - namespace: mission-control-agent - spec: - chart: - spec: - chart: mission-control-kubernetes - sourceRef: - kind: HelmRepository - name: flanksource - namespace: mission-control-agent - values: - clusterName: "testname" - interval: "" - " - `); + expect(writeText.mock["calls"][0][0]).toMatchSnapshot(); }); it("renders the Helm repository installation command with kube command", async () => { @@ -130,60 +98,6 @@ describe("InstallAgentModal", () => { await waitFor(() => { expect(writeText).toHaveBeenCalled(); }); - expect(writeText.mock["calls"][0][0]).toMatchInlineSnapshot(` - "apiVersion: v1 - kind: Namespace - metadata: - name: mission-control-agent - --- - apiVersion: source.toolkit.fluxcd.io/v1beta1 - kind: HelmRepository - metadata: - name: flanksource - namespace: mission-control-agent - spec: - interval: 5m0s - url: https://flanksource.github.io/charts - --- - apiVersion: helm.toolkit.fluxcd.io/v2beta1 - kind: HelmRelease - metadata: - name: mission-control - namespace: mission-control-agent - spec: - chart: - spec: - chart: mission-control-agent - sourceRef: - kind: HelmRepository - name: flanksource - namespace: mission-control-agent - interval: 1m - values: - upstream: - createSecret: true - host: http://localhost - username: token - agentName: testname - password: testtoken - --- - apiVersion: helm.toolkit.fluxcd.io/v2beta1 - kind: HelmRelease - metadata: - name: mission-control-kubernetes - namespace: mission-control-agent - spec: - chart: - spec: - chart: mission-control-kubernetes - sourceRef: - kind: HelmRepository - name: flanksource - namespace: mission-control-agent - values: - clusterName: "testname" - interval: "" - " - `); + expect(writeText.mock["calls"][0][0]).toMatchSnapshot(); }); }); diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap b/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap new file mode 100644 index 000000000..bf646d2d1 --- /dev/null +++ b/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InstallAgentModal renders the Helm repository installation command 1`] = ` +"helm repo add mission-control-agent flanksource/mission-control-agent + +helm repo update + +helm install mc-agent flanksource/mission-control-agent -n "mission-control-agent" \\ + --set upstream.createSecret=true \\ + --set upstream.host=http://localhost:3000 \\ + --set upstream.username=token \\ + --set upstream.password=password \\ + --set upstream.agentName=test-new-agent-instructions \\ + --set pushTelemetry.enabled=true \\ + --set pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions \\ + --create-namespace + +" +`; + +exports[`InstallAgentModal renders the Helm repository installation command with kube command 1`] = ` +"helm repo add mission-control-agent flanksource/mission-control-agent + +helm repo update + +helm install mc-agent flanksource/mission-control-agent -n "mission-control-agent" \\ + --set upstream.createSecret=true \\ + --set upstream.host=http://localhost:3000 \\ + --set upstream.username=token \\ + --set upstream.password=password \\ + --set upstream.agentName=test-new-agent-instructions \\ + --set pushTelemetry.enabled=true \\ + --set pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions \\ + --create-namespace + +helm install mc-agent-kubernetes flanksource/mission-control-kubernetes -n "mission-control-agent" \\ + --set clusterName: "test-new-agent-instructions" \\ + --set scraper.schedule: "30m" \\ +" +`; diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap b/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap new file mode 100644 index 000000000..dc77868e3 --- /dev/null +++ b/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InstallAgentModal renders the Helm repository installation command 1`] = ` +"apiVersion: v1 +kind: Namespace +metadata: + name: mission-control-agent +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: mission-control-agent + namespace: mission-control-agent +spec: + chart: + spec: + chart: mission-control-agent + sourceRef: + kind: HelmRepository + name: flanksource + namespace: mission-control-agent + interval: 5m0s + values: + upstream.createSecret=true + upstream.host=http://localhost:3000 + upstream.username=token + upstream.password=password + upstream.agentName=test-new-agent-instructions + pushTelemetry.enabled=true + pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions + + " +`; + +exports[`InstallAgentModal renders the Helm repository installation command with kube command 1`] = ` +"apiVersion: v1 +kind: Namespace +metadata: + name: mission-control-agent +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: mission-control-agent + namespace: mission-control-agent +spec: + chart: + spec: + chart: mission-control-agent + sourceRef: + kind: HelmRepository + name: flanksource + namespace: mission-control-agent + interval: 5m0s + values: + upstream.createSecret=true + upstream.host=http://localhost:3000 + upstream.username=token + upstream.password=password + upstream.agentName=test-new-agent-instructions + pushTelemetry.enabled=true + pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions + + " +`; From 57591e401ec207ca1bc236885fb710c8c8abda2a Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Wed, 9 Oct 2024 14:56:11 +0300 Subject: [PATCH 2/2] refactor: extract helm installation snippet from agent installation instructions Update src/ui/HelmSnippet/CLISnippet.tsx Co-authored-by: Moshe Immerman chore: update manifests chore: fix failing tests --- .../CLIInstallAgent.tsx | 43 ----- .../FluxInstallAgent.tsx | 81 --------- .../InstallAgentModal.tsx | 145 ++++++++--------- .../__tests__/CLIInstallAgent.unit.test.tsx | 87 ---------- .../__tests__/FluxInstallAgent.unit.test.tsx | 103 ------------ .../CLIInstallAgent.unit.test.tsx.snap | 40 ----- .../FluxInstallAgent.unit.test.tsx.snap | 65 -------- src/ui/HelmSnippet/CLISnippet.tsx | 66 ++++++++ src/ui/HelmSnippet/FluxSnippet.tsx | 79 +++++++++ .../HelmInstallationSnippets.stories.tsx | 34 ++++ .../HelmSnippet/HelmInstallationSnippets.tsx | 61 +++++++ .../__tests__/CLISnippet.unit.test.tsx | 29 ++++ .../__tests__/FluxSnippet.unit.test.tsx | 44 +++++ .../CLISnippet.unit.test.tsx.snap | 37 +++++ .../FluxSnippet.unit.test.tsx.snap | 83 ++++++++++ src/ui/HelmSnippet/__tests__/mocks/mocks.ts | 154 ++++++++++++++++++ 16 files changed, 658 insertions(+), 493 deletions(-) delete mode 100644 src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx delete mode 100644 src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx delete mode 100644 src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx delete mode 100644 src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx delete mode 100644 src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap delete mode 100644 src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap create mode 100644 src/ui/HelmSnippet/CLISnippet.tsx create mode 100644 src/ui/HelmSnippet/FluxSnippet.tsx create mode 100644 src/ui/HelmSnippet/HelmInstallationSnippets.stories.tsx create mode 100644 src/ui/HelmSnippet/HelmInstallationSnippets.tsx create mode 100644 src/ui/HelmSnippet/__tests__/CLISnippet.unit.test.tsx create mode 100644 src/ui/HelmSnippet/__tests__/FluxSnippet.unit.test.tsx create mode 100644 src/ui/HelmSnippet/__tests__/__snapshots__/CLISnippet.unit.test.tsx.snap create mode 100644 src/ui/HelmSnippet/__tests__/__snapshots__/FluxSnippet.unit.test.tsx.snap create mode 100644 src/ui/HelmSnippet/__tests__/mocks/mocks.ts diff --git a/src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx b/src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx deleted file mode 100644 index af2e7bc5b..000000000 --- a/src/components/Agents/InstalAgentInstruction/CLIInstallAgent.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import CodeBlock from "@flanksource-ui/ui/Code/CodeBlock"; -import Handlebars from "handlebars"; -import { useMemo } from "react"; -import { TemplateContextData } from "./InstallAgentModal"; - -// This a Handlebars template for the Helm command to install the agent and the -// kubernetes agent if the user has enabled it. -const helmCommand = `helm repo add {{ chart }} {{ repoName }}/{{ chart }} - -helm repo update - -helm install mc-agent flanksource/mission-control-agent -n "{{{ namespace }}}" \\ - {{#each values}} - --set {{{ this }}} \\ - {{/each}} - --create-namespace - -{{#if kubeValues }} -helm install mc-agent-kubernetes flanksource/mission-control-kubernetes -n "{{{ namespace }}}" \\ - {{#each kubeValues}} - --set {{{ this }}} \\ - {{/each}} -{{/if}} -`; - -const template = Handlebars.compile(helmCommand); - -type Props = { - data: TemplateContextData; -}; - -export default function CLIInstallAgent({ data }: Props) { - const helmCommandTemplate = useMemo(() => { - return template(data, {}); - }, [data]); - - return ( -
-

Copy the following command to install agent

- -
- ); -} diff --git a/src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx b/src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx deleted file mode 100644 index 762e453ae..000000000 --- a/src/components/Agents/InstalAgentInstruction/FluxInstallAgent.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { JSONViewer } from "@flanksource-ui/ui/Code/JSONViewer"; -import Handlebars from "handlebars"; -import { useMemo } from "react"; -import { TemplateContextData } from "./InstallAgentModal"; - -// This a Handlebars template for the HelmRelease to install the agent and the -// kubernetes agent if the user has enabled it. -const fluxTemplate = `apiVersion: v1 -kind: Namespace -metadata: - name: {{ namespace }} ---- -{{#if createRepository}} -apiVersion: source.toolkit.fluxcd.io/v1beta1 -kind: HelmRepository -metadata: - name: {{ repoName }} - namespace: {{ namespace }} -spec: - interval: 5m0s - url: https://flanksource.github.io/charts ---- -{{/if}} -apiVersion: helm.toolkit.fluxcd.io/v2beta1 -kind: HelmRelease -metadata: - name: {{{ chart }}} - namespace: {{ namespace }} -spec: - chart: - spec: - chart: {{{ chart }}} - sourceRef: - kind: HelmRepository - name: {{{ repoName }}} - namespace: {{{ namespace }}} - interval: 5m0s - values: -{{#each values}} - {{{ this }}} -{{/each}} - -{{#if kubeValues}} ---- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 -kind: HelmRelease -metadata: - name: mission-control-kubernetes - namespace: mission-control-agent -spec: - chart: - spec: - chart: mission-control-kubernetes - sourceRef: - kind: HelmRepository - name: flanksource - namespace: mission-control-agent - values: - {{#each kubeValues}} - {{{ this }}} - {{/each}} -{{/if}} - `; - -const template = Handlebars.compile(fluxTemplate); - -type Props = { - data: TemplateContextData; -}; - -export default function FluxInstallAgent({ data }: Props) { - const yaml = useMemo(() => { - return template(data, {}); - }, [data]); - - return ( -
- -
- ); -} diff --git a/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx b/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx index 95d5baccd..58bf4f2c7 100644 --- a/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx +++ b/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx @@ -1,27 +1,13 @@ -import { useMemo, useState } from "react"; +import HelmInstallationSnippets, { + ChartData +} from "@flanksource-ui/ui/HelmSnippet/HelmInstallationSnippets"; +import { useMemo } from "react"; import { GeneratedAgent } from "../../../api/services/agents"; import { Button } from "../../../ui/Buttons/Button"; import { Modal } from "../../../ui/Modal"; -import { Tab, Tabs } from "../../../ui/Tabs/Tabs"; import { AgentFormValues } from "../Add/AddAgentForm"; -import CLIInstallAgent from "./CLIInstallAgent"; -import FluxInstallAgent from "./FluxInstallAgent"; import { useAgentsBaseURL } from "./useAgentsBaseURL"; -export function WarningBox() { - return ( -
- - Access token will be shown only once. Please copy it and store it - securely. - -
- ); -} - export function MoreInfoBox() { return (
@@ -84,14 +70,6 @@ export function MoreInfoBox() { ); } -export type TemplateContextData = { - namespace?: string; - chart?: string; - repoName?: string; - values?: string[]; - kubeValues?: string[]; -}; - type Props = { isOpen: boolean; onClose: () => void; @@ -105,47 +83,82 @@ export default function InstallAgentModal({ generatedAgent, agentFormValues }: Props) { - const [activeTab, setActiveTab] = useState<"cli" | "flux">("cli"); const baseUrl = useAgentsBaseURL(); const data = useMemo(() => { const kubeOptions = agentFormValues?.kubernetes; const pushTelemetry = agentFormValues?.pushTelemetry ?? undefined; - return { - chart: "mission-control-agent", - namespace: "mission-control-agent", - repoName: "flanksource", - // You can add more values here to be passed to the template for the - // values section of the HelmRelease - values: [ - `upstream.createSecret=true`, - `upstream.host=${baseUrl}`, - `upstream.username=token`, - `upstream.password=${generatedAgent?.access_token}`, - `upstream.agentName=${agentFormValues?.name}`, - ...(pushTelemetry?.enabled - ? [ - `pushTelemetry.enabled=true`, - `pushTelemetry.topologyName=${ - pushTelemetry.topologyName - }-${agentFormValues?.name}` - ] - : []) - ], - // You can add more values here to be passed to the template for the - // kubeValues section of the HelmRelease - kubeValues: kubeOptions + return [ + { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + createNamespace: true, + createRepo: true, + updateHelmRepo: true, + releaseName: "mc-agent", + chartUrl: "https://flanksource.github.io/charts", + // You can add more values here to be passed to the template for the + // values section of the HelmRelease + values: [ + { + key: "upstream.createSecret", + value: "true" + }, + { + key: "upstream.host", + value: baseUrl + }, + { + key: "upstream.username", + value: "token" + }, + { + key: "upstream.password", + value: generatedAgent?.access_token + }, + { + key: "upstream.agentName", + value: agentFormValues?.name + }, + ...(pushTelemetry?.enabled + ? [ + { + key: "pushTelemetry.enabled", + value: "true" + }, + { + key: "pushTelemetry.topologyName", + value: `${pushTelemetry.topologyName}` + } + ] + : []) + ] + }, + ...(kubeOptions?.enabled ? [ - `clusterName: "${agentFormValues?.name}"`, - `scraper.schedule: "${kubeOptions.schedule}"` + { + chart: "mission-control-kubernetes", + namespace: "mission-control-agent", + repoName: "flanksource", + releaseName: "mc-agent-kubernetes", + values: [ + { + key: "clusterName", + value: agentFormValues?.name + }, + { + key: "scraper.schedule", + value: kubeOptions.schedule + } + ] + } ] - : [] - } satisfies TemplateContextData; + : []) + ] satisfies ChartData[]; }, [agentFormValues, baseUrl, generatedAgent]); - console.log(JSON.stringify(data, null, 2)); - return (
- setActiveTab(v as any)} - > - - - - - - - - - +
diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx b/src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx deleted file mode 100644 index 7c77f9591..000000000 --- a/src/components/Agents/InstalAgentInstruction/__tests__/CLIInstallAgent.unit.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import { AuthContext, FakeUser, Roles } from "../../../../context"; -import InstallAgentModal from "../CLIInstallAgent"; - -global.ResizeObserver = jest.fn().mockImplementation(() => ({ - observe: jest.fn(), - unobserve: jest.fn(), - disconnect: jest.fn() -})); - -const mockInput = { - chart: "mission-control-agent", - namespace: "mission-control-agent", - repoName: "flanksource", - values: [ - "upstream.createSecret=true", - "upstream.host=http://localhost:3000", - "upstream.username=token", - "upstream.password=password", - "upstream.agentName=test-new-agent-instructions", - "pushTelemetry.enabled=true", - "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" - ] -}; - -const mockInputWithKubOptions = { - chart: "mission-control-agent", - namespace: "mission-control-agent", - repoName: "flanksource", - values: [ - "upstream.createSecret=true", - "upstream.host=http://localhost:3000", - "upstream.username=token", - "upstream.password=password", - "upstream.agentName=test-new-agent-instructions", - "pushTelemetry.enabled=true", - "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" - ], - kubeValues: [ - 'clusterName: "test-new-agent-instructions"', - 'scraper.schedule: "30m"' - ] -}; - -describe("InstallAgentModal", () => { - it("renders the Helm repository installation command", () => { - render( - - - - ); - expect( - screen.getByText( - "helm repo add mission-control-agent flanksource/mission-control-agent", - { - exact: false - } - ).textContent - ).toMatchSnapshot(); - }); - - it("renders the Helm repository installation command with kube command", () => { - render( - - - - ); - expect( - screen.getByText( - "helm repo add mission-control-agent flanksource/mission-control-agent", - { - exact: false - } - ).textContent - ).toMatchSnapshot(); - }); -}); diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx b/src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx deleted file mode 100644 index 6358b7d68..000000000 --- a/src/components/Agents/InstalAgentInstruction/__tests__/FluxInstallAgent.unit.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { AuthContext, FakeUser, Roles } from "../../../../context"; -import FluxInstallAgent from "../FluxInstallAgent"; - -global.ResizeObserver = jest.fn().mockImplementation(() => ({ - observe: jest.fn(), - unobserve: jest.fn(), - disconnect: jest.fn() -})); - -const writeText = jest.fn(); - -const mockInput = { - chart: "mission-control-agent", - namespace: "mission-control-agent", - repoName: "flanksource", - values: [ - "upstream.createSecret=true", - "upstream.host=http://localhost:3000", - "upstream.username=token", - "upstream.password=password", - "upstream.agentName=test-new-agent-instructions", - "pushTelemetry.enabled=true", - "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" - ] -}; - -const mockInputWithKubOptions = { - chart: "mission-control-agent", - namespace: "mission-control-agent", - repoName: "flanksource", - values: [ - "upstream.createSecret=true", - "upstream.host=http://localhost:3000", - "upstream.username=token", - "upstream.password=password", - "upstream.agentName=test-new-agent-instructions", - "pushTelemetry.enabled=true", - "pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" - ], - kubeValues: [ - 'clusterName: "test-new-agent-instructions"', - 'scraper.schedule: "30m"' - ] -}; - -Object.assign(navigator, { - clipboard: { - writeText - } -}); - -describe("InstallAgentModal", () => { - it("renders the Helm repository installation command", async () => { - render( - - - - ); - - const btn = screen.getByTitle(/Copy to clipboard/i); - - fireEvent.click(btn); - - await waitFor(() => { - expect(writeText).toHaveBeenCalled(); - }); - expect(writeText.mock["calls"][0][0]).toMatchSnapshot(); - }); - - it("renders the Helm repository installation command with kube command", async () => { - render( - - - - ); - const btn = screen.getByTitle(/Copy to clipboard/i); - - fireEvent.click(btn); - - await waitFor(() => { - expect(writeText).toHaveBeenCalled(); - }); - expect(writeText.mock["calls"][0][0]).toMatchSnapshot(); - }); -}); diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap b/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap deleted file mode 100644 index bf646d2d1..000000000 --- a/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/CLIInstallAgent.unit.test.tsx.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InstallAgentModal renders the Helm repository installation command 1`] = ` -"helm repo add mission-control-agent flanksource/mission-control-agent - -helm repo update - -helm install mc-agent flanksource/mission-control-agent -n "mission-control-agent" \\ - --set upstream.createSecret=true \\ - --set upstream.host=http://localhost:3000 \\ - --set upstream.username=token \\ - --set upstream.password=password \\ - --set upstream.agentName=test-new-agent-instructions \\ - --set pushTelemetry.enabled=true \\ - --set pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions \\ - --create-namespace - -" -`; - -exports[`InstallAgentModal renders the Helm repository installation command with kube command 1`] = ` -"helm repo add mission-control-agent flanksource/mission-control-agent - -helm repo update - -helm install mc-agent flanksource/mission-control-agent -n "mission-control-agent" \\ - --set upstream.createSecret=true \\ - --set upstream.host=http://localhost:3000 \\ - --set upstream.username=token \\ - --set upstream.password=password \\ - --set upstream.agentName=test-new-agent-instructions \\ - --set pushTelemetry.enabled=true \\ - --set pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions \\ - --create-namespace - -helm install mc-agent-kubernetes flanksource/mission-control-kubernetes -n "mission-control-agent" \\ - --set clusterName: "test-new-agent-instructions" \\ - --set scraper.schedule: "30m" \\ -" -`; diff --git a/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap b/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap deleted file mode 100644 index dc77868e3..000000000 --- a/src/components/Agents/InstalAgentInstruction/__tests__/__snapshots__/FluxInstallAgent.unit.test.tsx.snap +++ /dev/null @@ -1,65 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InstallAgentModal renders the Helm repository installation command 1`] = ` -"apiVersion: v1 -kind: Namespace -metadata: - name: mission-control-agent ---- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 -kind: HelmRelease -metadata: - name: mission-control-agent - namespace: mission-control-agent -spec: - chart: - spec: - chart: mission-control-agent - sourceRef: - kind: HelmRepository - name: flanksource - namespace: mission-control-agent - interval: 5m0s - values: - upstream.createSecret=true - upstream.host=http://localhost:3000 - upstream.username=token - upstream.password=password - upstream.agentName=test-new-agent-instructions - pushTelemetry.enabled=true - pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions - - " -`; - -exports[`InstallAgentModal renders the Helm repository installation command with kube command 1`] = ` -"apiVersion: v1 -kind: Namespace -metadata: - name: mission-control-agent ---- -apiVersion: helm.toolkit.fluxcd.io/v2beta1 -kind: HelmRelease -metadata: - name: mission-control-agent - namespace: mission-control-agent -spec: - chart: - spec: - chart: mission-control-agent - sourceRef: - kind: HelmRepository - name: flanksource - namespace: mission-control-agent - interval: 5m0s - values: - upstream.createSecret=true - upstream.host=http://localhost:3000 - upstream.username=token - upstream.password=password - upstream.agentName=test-new-agent-instructions - pushTelemetry.enabled=true - pushTelemetry.topologyName=https://incident-commander.demo.aws.flanksource.com-test-new-agent-instructions - - " -`; diff --git a/src/ui/HelmSnippet/CLISnippet.tsx b/src/ui/HelmSnippet/CLISnippet.tsx new file mode 100644 index 000000000..29b37bd82 --- /dev/null +++ b/src/ui/HelmSnippet/CLISnippet.tsx @@ -0,0 +1,66 @@ +import CodeBlock from "@flanksource-ui/ui/Code/CodeBlock"; +import Handlebars from "handlebars"; +import { useMemo } from "react"; +import { ChartData } from "./HelmInstallationSnippets"; + +// This a Handlebars template for the Helm command to install the agent and the +// kubernetes agent if the user has enabled it. +const helmCommand = `{{#each charts}} +{{#if this.valueFile }} +cat > values.yaml << EOF +{{ this.valueFile }} +EOF + +{{/if}} +{{#if this.createRepo }} +helm repo add {{{ this.repoName }}} {{{ this.chartUrl }}} +{{/if}} +{{#if this.updateHelmRepo }} +helm repo update +{{/if}} + +helm install {{{ this.chart }}} {{{ this.repoName }}}/{{{ this.chart }}} -n "{{{ this.namespace }}}" \\ + {{#each this.values}} + --set {{{ this.key }}}={{{ this.value }}} {{#unless @last}} \\ \n{{/unless}}{{/each}}{{#if this.createNamespace }} \\ + --create-namespace {{#if this.valueFile }} \\ {{/if}} +{{/if}} {{#if this.valueFile }} \\ + --set-file values.yaml {{#if this.args}} \\ {{/if}} +{{/if }} {{#if this.args}} \\ + {{#each this.args}} + {{ this }} {{#unless @last}} \\ {{/unless}} + {{/each}} +{{/if}} {{#if this.wait }} \\ + --wait +{{/if}} + +{{/each}} +`; + +const template = Handlebars.compile(helmCommand); + +type Props = { + data: ChartData[]; +}; + +export default function CLIInstallAgent({ data }: Props) { + const helmCommandTemplate = useMemo(() => { + return template( + { + charts: data.map((chart) => { + return { + ...chart, + chartUrl: chart.chartUrl ?? "https://flanksource.github.io/charts" + }; + }) + }, + {} + ); + }, [data]); + + return ( +
+

Copy the following command to install the chart

+ +
+ ); +} diff --git a/src/ui/HelmSnippet/FluxSnippet.tsx b/src/ui/HelmSnippet/FluxSnippet.tsx new file mode 100644 index 000000000..08d92d297 --- /dev/null +++ b/src/ui/HelmSnippet/FluxSnippet.tsx @@ -0,0 +1,79 @@ +import { JSONViewer } from "@flanksource-ui/ui/Code/JSONViewer"; +import Handlebars from "handlebars"; +import { useMemo } from "react"; +import { ChartData } from "./HelmInstallationSnippets"; + +// This a Handlebars template for the HelmRelease to install the agent and the +// kubernetes agent if the user has enabled it. +const fluxTemplate = `{{#each charts }} +{{#if this.createNamespace}} +apiVersion: v1 +kind: Namespace +metadata: + name: {{ this.namespace }} +--- +{{/if}} +{{#if this.createRepo}} +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: {{ this.repoName }} + namespace: {{ this.namespace }} +spec: + interval: 5m0s + url: {{{ this.chartUrl }}} +--- +{{/if}} +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: {{{ this.chart }}} + namespace: {{ this.namespace }} +spec: + chart: + spec: + chart: {{{ this.chart }}} + sourceRef: + kind: HelmRepository + name: {{{ this.repoName }}} + namespace: {{{ this.namespace }}} + interval: 5m0s + values: + {{#if this.valueFile }} +{{ this.valueFile }} + {{/if}} +{{#each this.values}} + {{{ this.key }}}: {{{ this.value }}} +{{/each}} +--- +{{/each}}`; + +const template = Handlebars.compile(fluxTemplate); + +type Props = { + data: ChartData[]; +}; + +export default function FluxSnippet({ data }: Props) { + const yaml = useMemo(() => { + return template( + { + charts: data.map((data) => ({ + ...data, + chartUrl: data?.chartUrl ?? "https://flanksource.github.io/charts", + valueFile: data?.valueFile + ? // Indent the valueFile content + data?.valueFile?.replace(/^/gm, " ") + : undefined + })) + }, + {} + ); + }, [data]); + + return ( +
+ +
+ ); +} diff --git a/src/ui/HelmSnippet/HelmInstallationSnippets.stories.tsx b/src/ui/HelmSnippet/HelmInstallationSnippets.stories.tsx new file mode 100644 index 000000000..a10756fc2 --- /dev/null +++ b/src/ui/HelmSnippet/HelmInstallationSnippets.stories.tsx @@ -0,0 +1,34 @@ +import { Meta, StoryFn } from "@storybook/react"; +import HelmInstallationSnippets from "./HelmInstallationSnippets"; +import { + mockInput, + mockInputWithKubOptions, + mockInputWithValueFile +} from "./__tests__/mocks/mocks"; + +export default { + title: "ui/HelmInstallationSnippets", + component: HelmInstallationSnippets +} satisfies Meta; + +const Template: StoryFn = (args) => ( + +); + +export const Default = Template.bind({}); + +Default.args = { + charts: [mockInput] +}; + +export const WithKubeOptions = Template.bind({}); + +WithKubeOptions.args = { + charts: mockInputWithKubOptions +}; + +export const WithKubeOptionsAndValueFile = Template.bind({}); + +WithKubeOptionsAndValueFile.args = { + charts: mockInputWithValueFile +}; diff --git a/src/ui/HelmSnippet/HelmInstallationSnippets.tsx b/src/ui/HelmSnippet/HelmInstallationSnippets.tsx new file mode 100644 index 000000000..5fa757b16 --- /dev/null +++ b/src/ui/HelmSnippet/HelmInstallationSnippets.tsx @@ -0,0 +1,61 @@ +import { useState } from "react"; +import { Tab, Tabs } from "../Tabs/Tabs"; +import CLIInstallAgent from "./CLISnippet"; +import FluxSnippet from "./FluxSnippet"; + +export function WarningBox() { + return ( +
+ + Access token will be shown only once. Please copy it and store it + securely. + +
+ ); +} + +export type ChartData = { + namespace?: string; + createNamespace?: boolean; + chart?: string; + chartUrl?: string; + repoName?: string; + releaseName: string; + values?: { + key: string; + value?: string; + }[]; + args?: string[]; + createRepo?: boolean; + wait?: boolean; + valueFile?: string; + updateHelmRepo?: boolean; +}; + +type HelmInstallationSnippetsProps = { + charts: ChartData[]; + isWarningDisplayed?: boolean; +}; + +export default function HelmInstallationSnippets({ + charts, + isWarningDisplayed = false +}: HelmInstallationSnippetsProps) { + const [activeTab, setActiveTab] = useState<"flux" | "cli">("flux"); + + return ( + setActiveTab(v as any)}> + + + {isWarningDisplayed && } + + + + {isWarningDisplayed && } + + + ); +} diff --git a/src/ui/HelmSnippet/__tests__/CLISnippet.unit.test.tsx b/src/ui/HelmSnippet/__tests__/CLISnippet.unit.test.tsx new file mode 100644 index 000000000..3c5cb921d --- /dev/null +++ b/src/ui/HelmSnippet/__tests__/CLISnippet.unit.test.tsx @@ -0,0 +1,29 @@ +import { render, screen } from "@testing-library/react"; +import InstallAgentModal from "../CLISnippet"; +import { mockInput, mockInputWithKubOptions } from "./mocks/mocks"; + +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn() +})); + +describe("InstallAgentModal", () => { + it("renders the Helm repository installation command", () => { + render(); + expect( + screen.getByText("helm repo add flanksource", { + exact: false + }).textContent + ).toMatchSnapshot(); + }); + + it("renders the Helm repository installation command with kube command", () => { + render(); + expect( + screen.getByText("helm repo add flanksource", { + exact: false + }).textContent + ).toMatchSnapshot(); + }); +}); diff --git a/src/ui/HelmSnippet/__tests__/FluxSnippet.unit.test.tsx b/src/ui/HelmSnippet/__tests__/FluxSnippet.unit.test.tsx new file mode 100644 index 000000000..56a54b5f4 --- /dev/null +++ b/src/ui/HelmSnippet/__tests__/FluxSnippet.unit.test.tsx @@ -0,0 +1,44 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import FluxSnippet from "../FluxSnippet"; +import { mockInput, mockInputWithKubOptions } from "./mocks/mocks"; + +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn() +})); + +const writeText = jest.fn(); + +Object.assign(navigator, { + clipboard: { + writeText + } +}); + +describe("InstallAgentModal", () => { + it("renders the Helm repository installation command", async () => { + render(); + + const btn = screen.getByTitle(/Copy to clipboard/i); + + fireEvent.click(btn); + + await waitFor(() => { + expect(writeText).toHaveBeenCalled(); + }); + expect(writeText.mock["calls"][0][0]).toMatchSnapshot(); + }); + + it("renders the Helm repository installation command with kube command", async () => { + render(); + const btn = screen.getByTitle(/Copy to clipboard/i); + + fireEvent.click(btn); + + await waitFor(() => { + expect(writeText).toHaveBeenCalled(); + }); + expect(writeText.mock["calls"][0][0]).toMatchSnapshot(); + }); +}); diff --git a/src/ui/HelmSnippet/__tests__/__snapshots__/CLISnippet.unit.test.tsx.snap b/src/ui/HelmSnippet/__tests__/__snapshots__/CLISnippet.unit.test.tsx.snap new file mode 100644 index 000000000..69cb7e902 --- /dev/null +++ b/src/ui/HelmSnippet/__tests__/__snapshots__/CLISnippet.unit.test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InstallAgentModal renders the Helm repository installation command 1`] = ` +"helm repo add flanksource https://flanksource.github.io/charts + +helm install mission-control-agent flanksource/mission-control-agent -n "mission-control-agent" \\ + --set upstream.createSecret=true \\ + --set upstream.host=http://localhost:3000 \\ + --set upstream.username=token \\ + --set upstream.password=password \\ + --set upstream.agentName=test-new-agent-instructions \\ + --set pushTelemetry.enabled=true \\ + --set pushTelemetry.topologyName=incident-commander.demo.aws.flanksource.com-test-new-agent-instructions \\ + --create-namespace + +" +`; + +exports[`InstallAgentModal renders the Helm repository installation command with kube command 1`] = ` +"helm repo add flanksource https://flanksource.github.io/charts + +helm install mission-control-agent flanksource/mission-control-agent -n "mission-control-agent" \\ + --set upstream.createSecret=true \\ + --set upstream.host=http://localhost:3000 \\ + --set upstream.username=token \\ + --set upstream.password=password \\ + --set upstream.agentName=test-new-agent-instructions \\ + --set pushTelemetry.enabled=true \\ + --set pushTelemetry.topologyName=incident-commander.demo.aws.flanksource.com-test-new-agent-instructions \\ + --create-namespace + + +helm install mc-agent-kubernetes flanksource/mc-agent-kubernetes -n "mission-control-agent" \\ + --set clusterName=test-new-agent-instructions \\ + --set scraper.schedule=30m +" +`; diff --git a/src/ui/HelmSnippet/__tests__/__snapshots__/FluxSnippet.unit.test.tsx.snap b/src/ui/HelmSnippet/__tests__/__snapshots__/FluxSnippet.unit.test.tsx.snap new file mode 100644 index 000000000..d41e36220 --- /dev/null +++ b/src/ui/HelmSnippet/__tests__/__snapshots__/FluxSnippet.unit.test.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InstallAgentModal renders the Helm repository installation command 1`] = ` +"apiVersion: v1 +kind: Namespace +metadata: + name: mission-control-agent +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: flanksource + namespace: mission-control-agent +spec: + interval: 5m0s + url: https://flanksource.github.io/charts +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: mission-control-agent + namespace: mission-control-agent +spec: + chart: + spec: + chart: mission-control-agent + sourceRef: + kind: HelmRepository + name: flanksource + namespace: mission-control-agent + interval: 5m0s + values: + upstream.createSecret: true + upstream.host: http://localhost:3000 + upstream.username: token + upstream.password: password + upstream.agentName: test-new-agent-instructions + pushTelemetry.enabled: true + pushTelemetry.topologyName: incident-commander.demo.aws.flanksource.com-test-new-agent-instructions +--- +" +`; + +exports[`InstallAgentModal renders the Helm repository installation command with kube command 1`] = ` +"apiVersion: v1 +kind: Namespace +metadata: + name: mission-control-agent +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: flanksource + namespace: mission-control-agent +spec: + interval: 5m0s + url: https://flanksource.github.io/charts +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: mission-control-agent + namespace: mission-control-agent +spec: + chart: + spec: + chart: mission-control-agent + sourceRef: + kind: HelmRepository + name: flanksource + namespace: mission-control-agent + interval: 5m0s + values: + upstream.createSecret: true + upstream.host: http://localhost:3000 + upstream.username: token + upstream.password: password + upstream.agentName: test-new-agent-instructions + pushTelemetry.enabled: true + pushTelemetry.topologyName: incident-commander.demo.aws.flanksource.com-test-new-agent-instructions +--- +" +`; diff --git a/src/ui/HelmSnippet/__tests__/mocks/mocks.ts b/src/ui/HelmSnippet/__tests__/mocks/mocks.ts new file mode 100644 index 000000000..4a5fd6e51 --- /dev/null +++ b/src/ui/HelmSnippet/__tests__/mocks/mocks.ts @@ -0,0 +1,154 @@ +import { ChartData } from "../../HelmInstallationSnippets"; + +export const mockInput: ChartData = { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + createRepo: true, + releaseName: "mc-agent", + createNamespace: true, + values: [ + { + key: "upstream.createSecret", + value: "true" + }, + { + key: "upstream.host", + value: "http://localhost:3000" + }, + { + key: "upstream.username", + value: "token" + }, + { + key: "upstream.password", + value: "password" + }, + { + key: "upstream.agentName", + value: "test-new-agent-instructions" + }, + { + key: "pushTelemetry.enabled", + value: "true" + }, + { + key: "pushTelemetry.topologyName", + value: + "incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" + } + ] +}; + +export const mockInputWithKubOptions: ChartData[] = [ + { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + createRepo: true, + createNamespace: true, + releaseName: "mc-agent", + values: [ + { + key: "upstream.createSecret", + value: "true" + }, + { + key: "upstream.host", + value: "http://localhost:3000" + }, + { + key: "upstream.username", + value: "token" + }, + { + key: "upstream.password", + value: "password" + }, + { + key: "upstream.agentName", + value: "test-new-agent-instructions" + }, + { + key: "pushTelemetry.enabled", + value: "true" + }, + { + key: "pushTelemetry.topologyName", + value: + "incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" + } + ] + }, + { + chart: "mc-agent-kubernetes", + repoName: "flanksource", + namespace: "mission-control-agent", + releaseName: "mc-agent-kubernetes", + values: [ + { + key: "clusterName", + value: "test-new-agent-instructions" + }, + { + key: "scraper.schedule", + value: "30m" + } + ] + } +]; + +export const mockInputWithValueFile: ChartData[] = [ + { + chart: "mission-control-agent", + namespace: "mission-control-agent", + repoName: "flanksource", + createRepo: true, + releaseName: "mc-agent", + createNamespace: true, + values: [ + { + key: "upstream.createSecret", + value: "true" + }, + { + key: "upstream.host", + value: "http://localhost:3000" + }, + { + key: "upstream.username", + value: "token" + }, + { + key: "upstream.password", + value: "password" + }, + { + key: "upstream.agentName", + value: "test-new-agent-instructions" + }, + { + key: "pushTelemetry.enabled", + value: "true" + }, + { + key: "pushTelemetry.topologyName", + value: + "incident-commander.demo.aws.flanksource.com-test-new-agent-instructions" + } + ], + valueFile: `key: value +key2: value2 +key3: value3 +key4: value4` + }, + { + chart: "mc-agent-kubernetes", + repoName: "flanksource", + namespace: "mission-control-agent", + releaseName: "mc-agent-kubernetes", + valueFile: `key: value +key2: value2 +key3: value3` + } +];