├── src ├── index.d.ts ├── fileTypes.ts ├── commands │ ├── showGlobalState.ts │ ├── fluxCheckPrerequisites.ts │ ├── addSource.ts │ ├── showWorkloadsHelpMessage.ts │ ├── copyResourceName.ts │ ├── fluxCheck.ts │ ├── setCurrentKubernetesContext.ts │ ├── createFromTemplate.ts │ ├── openResource.ts │ ├── fluxReconcileGitRepositoryForPath.ts │ ├── addKustomization.ts │ ├── fluxReconcileWorkload.ts │ ├── setClusterProvider.ts │ ├── fluxReconcileSource.ts │ ├── showLogs.ts │ ├── trace.ts │ ├── createGitRepositoryForPath.ts │ ├── showInstalledVersions.ts │ ├── createKustomizationForPath.ts │ ├── resume.ts │ ├── showNewUserGuide.ts │ ├── suspend.ts │ ├── deleteSource.ts │ ├── deleteWorkload.ts │ └── createSource.ts ├── utils │ ├── utils.ts │ ├── typeUtils.ts │ ├── objectUtils.ts │ ├── jsonUtils.ts │ ├── getUri.ts │ └── stringUtils.ts ├── views │ ├── views.ts │ ├── nodes │ │ ├── anyResourceNode.ts │ │ ├── namespaceNode.ts │ │ ├── nodeContext.ts │ │ ├── bucketNode.ts │ │ ├── kustomizationNode.ts │ │ ├── helmReleaseNode.ts │ │ ├── gitRepositoryNode.ts │ │ ├── ociRepositoryNode.ts │ │ ├── helmRepositoryNode.ts │ │ ├── gitOpsTemplateNode.ts │ │ ├── documentationNode.ts │ │ └── clusterDeploymentNode.ts │ └── dataProviders │ │ ├── templateDataProvider.ts │ │ ├── documentationDataProvider.ts │ │ ├── sourceDataProvider.ts │ │ └── dataProvider.ts ├── webview-backend │ ├── configureGitOps │ │ ├── receiveMessage.ts │ │ ├── actions.ts │ │ ├── lib │ │ │ ├── createGeneric.ts │ │ │ ├── exportGeneric.ts │ │ │ └── createAzure.ts │ │ └── openWebview.ts │ ├── types.ts │ ├── createFromTemplate │ │ ├── receiveMessage.ts │ │ └── openWebview.ts │ └── README.md ├── flux │ ├── fluxTypes.ts │ ├── cliArgs.ts │ └── fluxUtils.ts ├── kubernetes │ ├── kubernetesUtils.ts │ └── types │ │ ├── kubernetesFileSchemes.ts │ │ ├── flux │ │ ├── object.ts │ │ ├── gitOpsTemplate.ts │ │ ├── bucket.ts │ │ └── helmRepository.ts │ │ └── kubernetesConfig.ts ├── errorable.ts ├── test │ ├── runTest.ts │ └── suite │ │ └── index.ts ├── extensionState.ts ├── extensionContext.ts ├── vscodeContext.ts ├── statusBar.ts ├── terminal.ts ├── globalState.ts ├── kuberesources.ts ├── output.ts └── azure │ └── azurePrereqs.ts ├── .gitignore ├── webview-ui ├── configureGitOps │ ├── .gitignore │ ├── src │ │ ├── lib │ │ │ ├── utils │ │ │ │ ├── fetchJson.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── debug.ts │ │ │ │ └── vscode.ts │ │ │ ├── bindDirectives.ts │ │ │ ├── params.ts │ │ │ ├── types.d.ts │ │ │ └── unwrapModel.ts │ │ ├── index.tsx │ │ ├── components │ │ │ ├── Source │ │ │ │ ├── NewSource │ │ │ │ │ ├── Common │ │ │ │ │ │ ├── Name.tsx │ │ │ │ │ │ └── Namespace.tsx │ │ │ │ │ ├── Bucket.tsx │ │ │ │ │ ├── Settings │ │ │ │ │ │ ├── Intervals.tsx │ │ │ │ │ │ ├── Azure.tsx │ │ │ │ │ │ ├── OCIRepository │ │ │ │ │ │ │ ├── OCITLS.tsx │ │ │ │ │ │ │ └── Panel.tsx │ │ │ │ │ │ ├── Bucket │ │ │ │ │ │ │ └── Panel.tsx │ │ │ │ │ │ ├── HelmRepository │ │ │ │ │ │ │ ├── Panel.tsx │ │ │ │ │ │ │ └── HelmConnection.tsx │ │ │ │ │ │ └── GitRepository │ │ │ │ │ │ │ ├── Panel.tsx │ │ │ │ │ │ │ └── GitConnection.tsx │ │ │ │ │ ├── HelmRepository.tsx │ │ │ │ │ ├── GitRepository.tsx │ │ │ │ │ └── OCIRepository.tsx │ │ │ │ ├── SelectSource.tsx │ │ │ │ └── NewSource.tsx │ │ │ ├── Common │ │ │ │ ├── HelpLink.tsx │ │ │ │ ├── TextInput.tsx │ │ │ │ ├── Checkbox.tsx │ │ │ │ └── ListSelect.tsx │ │ │ ├── Source.tsx │ │ │ └── Kustomization.tsx │ │ ├── App.tsx │ │ └── toolkit.d.ts │ ├── resources │ │ └── icons │ │ │ ├── flux-logo.png │ │ │ ├── azure-logo.png │ │ │ ├── gitops-logo.png │ │ │ ├── dark │ │ │ └── cloud.svg │ │ │ └── light │ │ │ └── cloud.svg │ ├── index.html │ ├── README.md │ ├── tsconfig.json │ ├── vite.config.ts │ └── package.json ├── createFromTemplate │ ├── .gitignore │ ├── src │ │ ├── lib │ │ │ ├── utils │ │ │ │ ├── fetchJson.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── debug.ts │ │ │ │ └── vscode.ts │ │ │ ├── model.ts │ │ │ └── types.d.ts │ │ ├── index.tsx │ │ ├── components │ │ │ ├── Common │ │ │ │ ├── RequiredWarning.tsx │ │ │ │ ├── ParamTextInput.tsx │ │ │ │ └── ParamSelect.tsx │ │ │ └── Main.tsx │ │ ├── App.tsx │ │ └── toolkit.d.ts │ ├── resources │ │ └── icons │ │ │ ├── azure-logo.png │ │ │ ├── flux-logo.png │ │ │ ├── gitops-logo.png │ │ │ ├── dark │ │ │ └── cloud.svg │ │ │ └── light │ │ │ └── cloud.svg │ ├── README.md │ ├── index.html │ ├── tsconfig.json │ ├── vite.config.ts │ └── package.json └── Makefile ├── resources ├── icons │ ├── azure-logo.png │ ├── flux-logo.png │ ├── gitops-logo.png │ ├── dark │ │ └── cloud.svg │ └── light │ │ └── cloud.svg ├── images │ └── newUserGuide │ │ ├── 07-logs.png │ │ ├── 09-docs.png │ │ ├── 08-trace.png │ │ ├── 05-workloads.gif │ │ ├── 06-reconcile.gif │ │ ├── 01-enable-gitops.gif │ │ ├── 02-create-source.gif │ │ ├── 03-describe-source.gif │ │ └── 04-create-kustomization.gif └── Videos ├── docs └── images │ ├── vscode-context-menu.png │ ├── vscode-gitops-tools.png │ ├── vscode-install-flux.png │ ├── vscode-gitops-commands.png │ ├── vscode-templates-view.png │ ├── release-workflow-dispatch.png │ ├── vscode-configure-gitops.png │ └── vscode-templates-config.png ├── .github ├── ISSUE_TEMPLATE │ ├── 1_feature_request.md │ └── 2_bug_report.yml ├── scripts │ └── updateEdgeVersion.js └── workflows │ └── ci.yml ├── .editorconfig ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── media ├── reset.css ├── jsconfig.json ├── newUserGuide.css ├── createSource.css └── vscode.css ├── tsconfig.json ├── SECURITY.md ├── .vscodeignore ├── webpack.config.js └── .eslintrc.json /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'tinytim'; 2 | declare module 'shell-escape-tag'; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | out 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | build/* 5 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | build/* 5 | -------------------------------------------------------------------------------- /resources/icons/azure-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/icons/azure-logo.png -------------------------------------------------------------------------------- /resources/icons/flux-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/icons/flux-logo.png -------------------------------------------------------------------------------- /resources/icons/gitops-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/icons/gitops-logo.png -------------------------------------------------------------------------------- /docs/images/vscode-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/vscode-context-menu.png -------------------------------------------------------------------------------- /docs/images/vscode-gitops-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/vscode-gitops-tools.png -------------------------------------------------------------------------------- /docs/images/vscode-install-flux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/vscode-install-flux.png -------------------------------------------------------------------------------- /docs/images/vscode-gitops-commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/vscode-gitops-commands.png -------------------------------------------------------------------------------- /docs/images/vscode-templates-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/vscode-templates-view.png -------------------------------------------------------------------------------- /docs/images/release-workflow-dispatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/release-workflow-dispatch.png -------------------------------------------------------------------------------- /docs/images/vscode-configure-gitops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/vscode-configure-gitops.png -------------------------------------------------------------------------------- /docs/images/vscode-templates-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/docs/images/vscode-templates-config.png -------------------------------------------------------------------------------- /resources/images/newUserGuide/07-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/07-logs.png -------------------------------------------------------------------------------- /resources/images/newUserGuide/09-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/09-docs.png -------------------------------------------------------------------------------- /resources/images/newUserGuide/08-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/08-trace.png -------------------------------------------------------------------------------- /resources/images/newUserGuide/05-workloads.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/05-workloads.gif -------------------------------------------------------------------------------- /resources/images/newUserGuide/06-reconcile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/06-reconcile.gif -------------------------------------------------------------------------------- /src/fileTypes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines supported files types for kubernetes object configs. 3 | */ 4 | export enum FileTypes { 5 | Yaml = 'yaml', 6 | } 7 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/utils/fetchJson.ts: -------------------------------------------------------------------------------- 1 | export async function fetchJson(url: string) { 2 | return await (await fetch(url)).json() as any; 3 | } 4 | -------------------------------------------------------------------------------- /resources/images/newUserGuide/01-enable-gitops.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/01-enable-gitops.gif -------------------------------------------------------------------------------- /resources/images/newUserGuide/02-create-source.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/02-create-source.gif -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/lib/utils/fetchJson.ts: -------------------------------------------------------------------------------- 1 | export async function fetchJson(url: string) { 2 | return await (await fetch(url)).json() as any; 3 | } 4 | -------------------------------------------------------------------------------- /resources/images/newUserGuide/03-describe-source.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/03-describe-source.gif -------------------------------------------------------------------------------- /src/commands/showGlobalState.ts: -------------------------------------------------------------------------------- 1 | import { globalState } from '../extension'; 2 | 3 | export function showGlobalState() { 4 | globalState.showGlobalStateValue(); 5 | } 6 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/resources/icons/flux-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/webview-ui/configureGitOps/resources/icons/flux-logo.png -------------------------------------------------------------------------------- /resources/images/newUserGuide/04-create-kustomization.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/resources/images/newUserGuide/04-create-kustomization.gif -------------------------------------------------------------------------------- /webview-ui/configureGitOps/resources/icons/azure-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/webview-ui/configureGitOps/resources/icons/azure-logo.png -------------------------------------------------------------------------------- /webview-ui/configureGitOps/resources/icons/gitops-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/webview-ui/configureGitOps/resources/icons/gitops-logo.png -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/resources/icons/azure-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/webview-ui/createFromTemplate/resources/icons/azure-logo.png -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/resources/icons/flux-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/webview-ui/createFromTemplate/resources/icons/flux-logo.png -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/resources/icons/gitops-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weaveworks/vscode-gitops-tools/HEAD/webview-ui/createFromTemplate/resources/icons/gitops-logo.png -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web'; 3 | import App from './App'; 4 | 5 | render(() => , document.getElementById('root') as HTMLElement); 6 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web'; 3 | import App from './App'; 4 | 5 | render(() => , document.getElementById('root') as HTMLElement); 6 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Resolve the promise with a delay. 4 | * @param ms delay in milliseconds 5 | */ 6 | export async function delay(ms: number) { 7 | return new Promise(resolve => setTimeout(resolve, ms)); 8 | } 9 | -------------------------------------------------------------------------------- /webview-ui/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | npm --prefix ./configureGitOps install 3 | npm --prefix ./createFromTemplate install 4 | 5 | build: 6 | npm --prefix ./configureGitOps run build 7 | npm --prefix ./createFromTemplate run build 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.yml] 12 | indent_style = space -------------------------------------------------------------------------------- /src/commands/fluxCheckPrerequisites.ts: -------------------------------------------------------------------------------- 1 | import { shell } from '../shell'; 2 | 3 | /** 4 | * Runs `flux check --pre` command in the output view. 5 | */ 6 | export async function checkFluxPrerequisites() { 7 | return await shell.execWithOutput('flux check --pre'); 8 | } 9 | -------------------------------------------------------------------------------- /src/commands/addSource.ts: -------------------------------------------------------------------------------- 1 | import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; 2 | 3 | /** 4 | * Open ConfigureGitops webview with 'New Source' tab open 5 | */ 6 | export async function addSource() { 7 | openConfigureGitOpsWebview(false); 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "amodio.tsl-problem-matcher", 7 | "editorconfig.editorconfig" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/commands/showWorkloadsHelpMessage.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | export function showWorkloadsHelpMessage() { 4 | window.showInformationMessage('Workloads include Helm Releases and Kustomizations, as well as a tree of all other objects created by them.', { modal: true }); 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/typeUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Keep autocomplete for the union type with `| string`. 3 | * https://github.com/microsoft/TypeScript/issues/29729 4 | */ 5 | export type LiteralUnion = T | (U & Record); 6 | 7 | export interface ParamsDictionary { 8 | [index: string]: any; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/objectUtils.ts: -------------------------------------------------------------------------------- 1 | export function removeEmptyStrings(obj: { [x: string]: any; }) { 2 | for (const key in obj) { 3 | if(obj[key] === '') { 4 | obj[key] = undefined; 5 | } 6 | 7 | if(typeof obj[key] === 'object') { 8 | obj[key] = removeEmptyStrings(obj[key]); 9 | } 10 | } 11 | 12 | return obj; 13 | } 14 | -------------------------------------------------------------------------------- /src/views/views.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * GitOps view ids. 3 | */ 4 | export const enum Views { 5 | ClustersView = 'gitops.views.clusters', 6 | SourcesView = 'gitops.views.sources', 7 | WorkloadsView = 'gitops.views.workloads', 8 | TemplatesView = 'gitops.views.templates', 9 | DocumentationView = 'gitops.views.documentation', 10 | } 11 | -------------------------------------------------------------------------------- /media/reset.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 13px; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | ol, 21 | ul { 22 | margin: 0; 23 | padding: 0; 24 | font-weight: normal; 25 | } 26 | 27 | img { 28 | max-width: 100%; 29 | height: auto; 30 | } -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Common/Name.tsx: -------------------------------------------------------------------------------- 1 | import TextInput from 'components/Common/TextInput'; 2 | import { source } from 'lib/model'; 3 | 4 | function Name() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | 13 | export default Name; 14 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | type KubernetesObject = { kind: any; metadata: { name: any; namespace: any; }; }; 2 | 3 | export function capitalize(str: string): string { 4 | return str.charAt(0).toUpperCase() + str.slice(1); 5 | } 6 | 7 | 8 | export function namespacedSource(s: KubernetesObject): string { 9 | return `${s.kind}/${s.metadata.name}.${s.metadata.namespace}`; 10 | } 11 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/lib/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | type KubernetesObject = { kind: any; metadata: { name: any; namespace: any; }; }; 2 | 3 | export function capitalize(str: string): string { 4 | return str.charAt(0).toUpperCase() + str.slice(1); 5 | } 6 | 7 | 8 | export function namespacedSource(s: KubernetesObject): string { 9 | return `${s.kind}/${s.metadata.name}.${s.metadata.namespace}`; 10 | } 11 | -------------------------------------------------------------------------------- /src/webview-backend/configureGitOps/receiveMessage.ts: -------------------------------------------------------------------------------- 1 | import { WebviewPanel } from 'vscode'; 2 | import { actionCreate, actionYAML } from './actions'; 3 | 4 | export async function receiveMessage(message: any, panel: WebviewPanel) { 5 | switch (message.action) { 6 | case 'create': 7 | actionCreate(message.data); 8 | panel.dispose(); 9 | return; 10 | case 'show-yaml': 11 | actionYAML(message.data); 12 | return; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /media/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "jsx": "preserve", 6 | "checkJs": true, 7 | "strict": true, 8 | "strictFunctionTypes": true, 9 | "lib": [ 10 | "DOM", 11 | "DOM.Iterable" 12 | ] 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "**/node_modules/*" 17 | ], 18 | "typeAcquisition": { 19 | "include": [ 20 | "@types/vscode-webview" 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /resources/Videos: -------------------------------------------------------------------------------- 1 | resources: 2 | - youtube:Bmh7kKYLIhY 3 | title: "Flux Security & Scalability using VS Code GitOps Extension- Juozas Gaigalas, Weaveworks" 4 | date: 2022-11-16 5 | - youtube:QRZTc6hlCjI 6 | title: "GitOps Days 2022: Introducing Flux Visual Studio Code Extension in Public Beta by Kingdon Barrett" 7 | date: 2022-06-09 8 | - youtube:p8hTP2pPtqc 9 | title: "Weave GitOps - VS Code Plugin, Weaveworks" 10 | date: 2022-05-11 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2019", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2019" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, /* enable all strict type-checking options */ 12 | "forceConsistentCasingInFileNames": true, 13 | "esModuleInterop": true 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | ".vscode-test", 18 | "webview-ui" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Configure GitOps 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | At Weaveworks we take security very seriously, and value our close relationship with members of the security community. 4 | 5 | ## Supported Versions 6 | 7 | This project does not backport fixes to older versions, so please ensure you are always using the latest version to benefit from all fixes and security patches. 8 | 9 | ## Reporting a Vulnerability 10 | 11 | To submit a security bug report please e-mail us at security@weave.works. 12 | -------------------------------------------------------------------------------- /src/flux/fluxTypes.ts: -------------------------------------------------------------------------------- 1 | 2 | export type FluxSource = 'source git' | 'source oci' | 'source helm' | 'source bucket'; 3 | export type FluxWorkload = 'helmrelease' | 'kustomization'; 4 | 5 | /** 6 | * Object resulting from running `flux tree`. 7 | */ 8 | export interface FluxTreeResources { 9 | resource: { 10 | Namespace: string; 11 | Name: string; 12 | GroupKind: { 13 | Group: string; 14 | Kind: string; 15 | }; 16 | }; 17 | resources?: FluxTreeResources[]; 18 | } 19 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/README.md: -------------------------------------------------------------------------------- 1 | # `webview-ui` Directory 2 | 3 | This directory contains all of the code that will be executed within the webview context. It can be thought of as the place where all the "frontend" code of a webview is contained. 4 | 5 | Types of content that can be contained here: 6 | 7 | - Frontend framework code (i.e. SolidJS, React, Svelte, Vue, etc.) 8 | - JavaScript files 9 | - CSS files 10 | - Assets / resources (i.e. images, illustrations, etc.) 11 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/README.md: -------------------------------------------------------------------------------- 1 | # `webview-ui` Directory 2 | 3 | This directory contains all of the code that will be executed within the webview context. It can be thought of as the place where all the "frontend" code of a webview is contained. 4 | 5 | Types of content that can be contained here: 6 | 7 | - Frontend framework code (i.e. SolidJS, React, Svelte, Vue, etc.) 8 | - JavaScript files 9 | - CSS files 10 | - Assets / resources (i.e. images, illustrations, etc.) 11 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Create from Template 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/commands/copyResourceName.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'vscode'; 2 | import { SourceNode } from '../views/nodes/sourceNode'; 3 | import { WorkloadNode } from '../views/nodes/workloadNode'; 4 | 5 | /** 6 | * Copy to clipboard any resource node name. 7 | */ 8 | export function copyResourceName(resourceNode: SourceNode | WorkloadNode) { 9 | const name = resourceNode.resource.metadata.name; 10 | 11 | if (!name) { 12 | return; 13 | } 14 | 15 | env.clipboard.writeText(name); 16 | } 17 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Bucket.tsx: -------------------------------------------------------------------------------- 1 | import Name from './Common/Name'; 2 | import Namespace from './Common/Namespace'; 3 | import SettingsPanel from './Settings/Bucket/Panel'; 4 | import Connection from './Settings/Bucket/BucketConnection'; 5 | 6 | 7 | function Bucket() { 8 | return ( 9 |
10 | 11 | 12 | 13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default Bucket; 20 | -------------------------------------------------------------------------------- /src/kubernetes/kubernetesUtils.ts: -------------------------------------------------------------------------------- 1 | import { KubernetesObject } from './types/kubernetesTypes'; 2 | 3 | export function sortByMetadataName(items: Type[]): Type[] { 4 | return items.sort((i1: any, i2: any) => { 5 | if (i1.metadata.name && i2.metadata.name) { 6 | if (i1.metadata.name > i2.metadata.name) { 7 | return 1; 8 | } 9 | if (i1.metadata.name < i2.metadata.name) { 10 | return -1; 11 | } 12 | } 13 | return 0; 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Common/Namespace.tsx: -------------------------------------------------------------------------------- 1 | import ListSelect from 'components/Common/ListSelect'; 2 | import { params } from 'lib/params'; 3 | 4 | function Namespace() { 5 | return ( 6 |
7 | 8 |
9 | params.namespaces} 12 | class="medium"/> 13 |
14 |
15 | ); 16 | } 17 | 18 | export default Namespace; 19 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/components/Common/RequiredWarning.tsx: -------------------------------------------------------------------------------- 1 | import { missingParams } from 'components/Main'; 2 | import { Show } from 'solid-js'; 3 | 4 | const isMissing = (name: string) => missingParams().includes(name); 5 | 6 | export function RequiredWarning(props: { name: string; }) { 7 | const name = props.name; 8 | 9 | return( 10 | 11 | 12 | (required) 13 | 14 | 15 | ); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/errorable.ts: -------------------------------------------------------------------------------- 1 | export interface Succeeded { 2 | readonly succeeded: true; 3 | readonly result: T; 4 | } 5 | 6 | export interface Failed { 7 | readonly succeeded: false; 8 | readonly error: string[]; 9 | } 10 | 11 | export type Errorable = Succeeded | Failed; 12 | 13 | export function succeeded(e: Errorable): e is Succeeded { 14 | return e.succeeded; 15 | } 16 | 17 | export function failed(e: Errorable): e is Failed { 18 | return !e.succeeded; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/commands/fluxCheck.ts: -------------------------------------------------------------------------------- 1 | import safesh from 'shell-escape-tag'; 2 | import { shell } from '../shell'; 3 | import { ClusterContextNode } from '../views/nodes/clusterContextNode'; 4 | 5 | /** 6 | * Runs `flux check` command for selected cluster in the output view. 7 | * @param clusterNode target cluster node (from tree node context menu) 8 | */ 9 | export async function fluxCheck(clusterNode: ClusterContextNode) { 10 | shell.execWithOutput(safesh`flux check --context ${clusterNode.contextName}`); 11 | } 12 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/bindDirectives.ts: -------------------------------------------------------------------------------- 1 | import { Tabs } from '@microsoft/fast-foundation'; 2 | import { Checkbox } from '@vscode/webview-ui-toolkit'; 3 | import { createEffect, createRenderEffect, onMount } from 'solid-js'; 4 | import { debug } from './utils/debug'; 5 | import { source, setSource } from './model'; 6 | 7 | export function bindChangeTabsFunc(el: Tabs, update: any) { 8 | update()(el); 9 | 10 | el.addEventListener('change', (e: Event) => { 11 | update()(el); 12 | }); 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Common/HelpLink.tsx: -------------------------------------------------------------------------------- 1 | 2 | const baseUrl = 'https://fluxcd.io/flux/components/'; 3 | export function ToolkitHelpLink(props: any) { 4 | return HelpLink({...props, href: baseUrl + props.href }); 5 | } 6 | 7 | 8 | export function HelpLink(props: any) { 9 | return ( 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/Intervals.tsx: -------------------------------------------------------------------------------- 1 | import TextInput from 'components/Common/TextInput'; 2 | import { source, setSource } from 'lib/model'; 3 | 4 | function SettingsIntervals() { 5 | return ( 6 |
7 |
8 | 9 |
10 | 11 |
12 |
13 |
14 | ); 15 | } 16 | 17 | export default SettingsIntervals; 18 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "target": "esnext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite/client"], 12 | "noEmit": true, 13 | "isolatedModules": true, 14 | "lib": ["esnext", "dom"], 15 | "baseUrl": "./src", 16 | }, 17 | "include": ["./src"] 18 | } 19 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "target": "esnext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite/client"], 12 | "noEmit": true, 13 | "isolatedModules": true, 14 | "lib": ["esnext", "dom"], 15 | "baseUrl": "./src", 16 | }, 17 | "include": ["./src"] 18 | } 19 | -------------------------------------------------------------------------------- /media/newUserGuide.css: -------------------------------------------------------------------------------- 1 | /* h1 { 2 | color: aquamarine; 3 | } */ 4 | 5 | img.screencap { 6 | width: 480px; 7 | height: 320px; 8 | } 9 | 10 | p { 11 | margin: 1rem 0; 12 | } 13 | 14 | ul { 15 | margin-bottom: 1.5rem; 16 | } 17 | 18 | 19 | 20 | h2, h3 { 21 | font-weight: 700; 22 | margin: 1.5rem 0 0.25rem 0; 23 | 24 | } 25 | 26 | h2 + h3 { 27 | margin-top: 0.5rem; 28 | } 29 | 30 | p { 31 | margin: 0.75rem 0; 32 | } 33 | 34 | p.ul-text { 35 | margin: 1rem 0 0 0; 36 | } 37 | 38 | .bottom-padding { 39 | margin-top: 40rem; 40 | } -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/params.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'solid-js/store'; 2 | import { debug } from './utils/debug'; 3 | 4 | export interface ParamsDictionary { 5 | [index: string]: any; 6 | } 7 | 8 | export const [params, setParams] = createStore({} as ParamsDictionary); 9 | 10 | 11 | export function updateParams(newParams: ParamsDictionary) { 12 | for(const [param, value] of Object.entries(newParams)) { 13 | setParams(param, value); 14 | } 15 | 16 | debug('params set:'); 17 | debug(JSON.stringify(params)); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/SelectSource.tsx: -------------------------------------------------------------------------------- 1 | import { params } from 'lib/params'; 2 | import ListSelect from 'components/Common/ListSelect'; 3 | import { namespacedSource } from 'lib/utils/helpers'; 4 | 5 | 6 | const namespacedSources = () => params.sources.map((s: any) => namespacedSource(s)).sort(); 7 | 8 | function SelectSource() { 9 | return ( 10 |
11 | 14 |
15 | ); 16 | } 17 | 18 | export default SelectSource; 19 | -------------------------------------------------------------------------------- /src/kubernetes/types/kubernetesFileSchemes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines Kubernetes file schemes 3 | * used by the virtual Kubernetes Tools file system provider 4 | * to open kubernetes object config files in vscode editor. 5 | * @see https://github.com/Azure/vscode-kubernetes-tools/blob/master/src/kuberesources.virtualfs.ts 6 | */ 7 | export const enum KubernetesFileSchemes { 8 | Resource = 'k8smsx', 9 | ReadonlyResource = 'k8smsxro', 10 | KubectlResource = 'loadkubernetescore', 11 | DescribeResource = 'kubernetesdescribe', 12 | HelmResource = 'helmget', 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/jsonUtils.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { telemetry } from '../extension'; 3 | import { TelemetryErrorEventNames } from '../telemetry'; 4 | 5 | export function parseJson(jsonString: string): any { 6 | let jsonData: any; 7 | 8 | try { 9 | jsonData = JSON.parse(jsonString.trim()); 10 | } catch(e: unknown) { 11 | window.showErrorMessage(`JSON.parse() failed ${e}`); 12 | telemetry.sendError(TelemetryErrorEventNames.UNCAUGHT_EXCEPTION, new Error('parseJson() failed')); 13 | return; 14 | } 15 | 16 | return jsonData; 17 | } 18 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .github 2 | .gitignore 3 | webpack.config.js 4 | 5 | .vscode/** 6 | .vscode-test/** 7 | out/** 8 | node_modules/** 9 | src/** 10 | test 11 | node_modules 12 | 13 | **/tsconfig.json 14 | **/.eslintrc.json 15 | **/*.map 16 | **/*.ts 17 | 18 | 19 | webview-ui/**/.vscode/** 20 | webview-ui/**/.vscode-test/** 21 | webview-ui/**/out/** 22 | webview-ui/**/node_modules/** 23 | webview-ui/**/src/** 24 | webview-ui/**/test 25 | webview-ui/**/node_modules 26 | webview-ui/**/**/tsconfig.json 27 | webview-ui/**/**/.eslintrc.json 28 | webview-ui/**/**/*.map 29 | webview-ui/**/**/*.ts 30 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | 5 | 6 | export default defineConfig({ 7 | plugins: [tsconfigPaths(), solidPlugin()], 8 | build: { 9 | target: 'esnext', 10 | polyfillDynamicImport: false, 11 | outDir: 'build', 12 | rollupOptions: { 13 | output: { 14 | entryFileNames: 'assets/[name].js', 15 | chunkFileNames: 'assets/[name].js', 16 | assetFileNames: 'assets/[name].[ext]', 17 | }, 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | 5 | 6 | export default defineConfig({ 7 | plugins: [tsconfigPaths(), solidPlugin()], 8 | build: { 9 | target: 'esnext', 10 | polyfillDynamicImport: false, 11 | outDir: 'build', 12 | rollupOptions: { 13 | output: { 14 | entryFileNames: 'assets/[name].js', 15 | chunkFileNames: 'assets/[name].js', 16 | assetFileNames: 'assets/[name].[ext]', 17 | }, 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/commands/setCurrentKubernetesContext.ts: -------------------------------------------------------------------------------- 1 | import { kubernetesTools } from '../kubernetes/kubernetesTools'; 2 | import { ClusterContextNode } from '../views/nodes/clusterContextNode'; 3 | import { refreshAllTreeViews } from '../views/treeViews'; 4 | 5 | /** 6 | * Sets Kubernetes context and refreshes tree views if needed. 7 | */ 8 | export async function setCurrentKubernetesContext(clusterContext: ClusterContextNode): Promise { 9 | const setContextResult = await kubernetesTools.setCurrentContext(clusterContext.contextName); 10 | if (setContextResult?.isChanged) { 11 | refreshAllTreeViews(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/views/nodes/anyResourceNode.ts: -------------------------------------------------------------------------------- 1 | import { KubernetesObject } from '@kubernetes/client-node'; 2 | import { TreeNode } from './treeNode'; 3 | 4 | /** 5 | * Defines any kubernetes resourse. 6 | */ 7 | export class AnyResourceNode extends TreeNode { 8 | 9 | /** 10 | * kubernetes resource metadata 11 | */ 12 | resource: KubernetesObject; 13 | 14 | constructor(anyResource: KubernetesObject) { 15 | super(anyResource.metadata?.name || ''); 16 | 17 | this.description = anyResource.kind; 18 | 19 | // save metadata reference 20 | this.resource = anyResource; 21 | } 22 | 23 | get tooltip() { 24 | return ''; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/views/nodes/namespaceNode.ts: -------------------------------------------------------------------------------- 1 | import { KubernetesObjectKinds, Namespace } from '../../kubernetes/types/kubernetesTypes'; 2 | import { TreeNode } from './treeNode'; 3 | 4 | /** 5 | * Defines any kubernetes resourse. 6 | */ 7 | export class NamespaceNode extends TreeNode { 8 | 9 | /** 10 | * kubernetes resource metadata 11 | */ 12 | resource: Namespace; 13 | 14 | constructor(namespace: Namespace) { 15 | super(namespace.metadata?.name || ''); 16 | 17 | this.description = KubernetesObjectKinds.Namespace; 18 | 19 | this.resource = namespace; 20 | } 21 | 22 | get contexts() { 23 | return [KubernetesObjectKinds.Namespace]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/createFromTemplate.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { GitOpsTemplateNode } from '../views/nodes/gitOpsTemplateNode'; 3 | import { openCreateFromTemplatePanel } from '../webview-backend/createFromTemplate/openWebview'; 4 | 5 | export async function createFromTemplate(templateNode?: GitOpsTemplateNode) { 6 | // const name = await window.showQuickPick(['cluster-template-development', 'cluster-template-development-plus-kubelogin']); 7 | 8 | if(!templateNode || !templateNode.resource) { 9 | return; 10 | } 11 | 12 | // TODO: const resource = yaml.parseDocument(yourYamlFile); 13 | openCreateFromTemplatePanel(templateNode.resource); 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/openResource.ts: -------------------------------------------------------------------------------- 1 | import { Uri, window, workspace } from 'vscode'; 2 | import { telemetry } from '../extension'; 3 | import { TelemetryErrorEventNames } from '../telemetry'; 4 | 5 | /** 6 | * Open resource in the editor 7 | * @param uri target Uri to open 8 | */ 9 | export async function openResource(uri: Uri): Promise { 10 | return await workspace.openTextDocument(uri).then(document => { 11 | if (document) { 12 | window.showTextDocument(document); 13 | } 14 | }, 15 | error => { 16 | window.showErrorMessage(`Error loading document: ${error}`); 17 | telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_OPEN_RESOURCE); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/lib/model.ts: -------------------------------------------------------------------------------- 1 | import { createSignal } from 'solid-js'; 2 | import { ReactiveMap } from '@solid-primitives/map'; 3 | 4 | 5 | export type TemplateParam = { 6 | name: string; 7 | description?: string; 8 | required?: boolean; 9 | options?: string[]; 10 | default?: string; 11 | }; 12 | 13 | 14 | export type GitOpsTemplate = { 15 | name?: string; 16 | namespace?: string; 17 | description?: string; 18 | params: TemplateParam[]; 19 | folder?: string; 20 | }; 21 | 22 | const t: GitOpsTemplate = {params: []}; 23 | 24 | 25 | export const [gitOpsTemplate, setGitOpsTemplate] = createSignal(t); 26 | export const values = new ReactiveMap(); 27 | 28 | -------------------------------------------------------------------------------- /src/utils/getUri.ts: -------------------------------------------------------------------------------- 1 | import { Uri, Webview } from 'vscode'; 2 | 3 | /** 4 | * A helper function which will get the webview URI of a given file or resource. 5 | * 6 | * @remarks This URI can be used within a webview's HTML as a link to the 7 | * given file/resource. 8 | * 9 | * @param webview A reference to the extension webview 10 | * @param extensionUri The URI of the directory containing the extension 11 | * @param pathList An array of strings representing the path to a file/resource 12 | * @returns A URI pointing to the file/resource 13 | */ 14 | export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { 15 | return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)); 16 | } 17 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import which from 'which'; 3 | import * as shelljs from 'shelljs'; 4 | 5 | 6 | import { runTests, downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from '@vscode/test-electron'; 7 | 8 | async function main() { 9 | try { 10 | // --extensionDevelopmentPath 11 | let extensionDevelopmentPath = path.resolve(__dirname, '../../'); 12 | // --extensionTestsPath 13 | let extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 16 | } catch (err) { 17 | console.error('Failed to run tests'); 18 | console.error(err); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/extensionState.ts: -------------------------------------------------------------------------------- 1 | interface ExtensionStateMap { 2 | fluxVersion: string; 3 | } 4 | 5 | type ExtensionStateKey = keyof ExtensionStateMap; 6 | 7 | class ExtensionState { 8 | /** 9 | * All the items of the global state. 10 | */ 11 | private state: ExtensionStateMap = { 12 | fluxVersion: 'Not installed', 13 | }; 14 | 15 | 16 | get(stateKey: T): ExtensionStateMap[T] { 17 | return this.state[stateKey]; 18 | } 19 | 20 | set(stateKey: T, newValue: ExtensionStateMap[T]): void { 21 | this.state[stateKey] = newValue; 22 | } 23 | } 24 | 25 | /** 26 | * Global state (temporary, while extension is running). 27 | */ 28 | export const extensionState = new ExtensionState(); 29 | 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true, // set this to false to include "dist" folder in search results 10 | "src/test": true, 11 | }, 12 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 13 | "typescript.tsc.autoDetect": "off", 14 | "debug.node.autoAttach": "on" 15 | } -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Common/TextInput.tsx: -------------------------------------------------------------------------------- 1 | import { gitRepository, source, storeAccessors } from 'lib/model'; 2 | import { createEffect, onMount } from 'solid-js'; 3 | 4 | import { ReactiveInputProps } from 'lib/types'; 5 | 6 | 7 | 8 | function TextInput(props: ReactiveInputProps) { 9 | let inputElement: HTMLInputElement; 10 | 11 | const {get, set} = storeAccessors(props); 12 | 13 | onMount(() => { 14 | createEffect(() => inputElement.value = get()); 15 | 16 | inputElement.addEventListener('input', (e: Event) => set(inputElement.value)); 17 | }); 18 | 19 | return ( 20 | 21 | ); 22 | } 23 | 24 | export default TextInput; 25 | 26 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-create-from-template-webapp", 3 | "version": "0.5.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vite", 7 | "build": "vite build", 8 | "serve": "vite preview", 9 | "watch": "vite build --watch" 10 | }, 11 | "dependencies": { 12 | "@solid-primitives/map": "^0.3.2", 13 | "@vscode/codicons": "^0.0.32", 14 | "@vscode/webview-ui-toolkit": "^1.0.0", 15 | "solid-collapse": "^1.0.0", 16 | "solid-js": "^1.3.13" 17 | }, 18 | "devDependencies": { 19 | "@types/vscode-webview": "^1.57.0", 20 | "typescript": "^4.8.4", 21 | "vite": "^2.9.13", 22 | "vite-plugin-solid": "^2.2.6", 23 | "vite-tsconfig-paths": "^4.0.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/views/nodes/nodeContext.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines GitOps tree view node context values. 3 | */ 4 | export const enum NodeContext { 5 | 6 | // Cluster context values 7 | Cluster = 'cluster', 8 | CurrentCluster = 'currentCluster', 9 | NotCurrentCluster = 'notCurrentCluster', 10 | // Need both bc there's a time when it's not known whether it's enabled or not 11 | // and bc it's a string context with multiple contexts delimited by `;`, not boolean 12 | ClusterGitOpsEnabled = 'clusterGitOpsEnabled', 13 | ClusterGitOpsNotEnabled = 'clusterGitOpsNotEnabled', 14 | 15 | // resource contexts 16 | AzureFluxConfig = 'azureFluxConfig', 17 | NotAzureFluxConfig = 'NotAzureFluxConfig', 18 | 19 | // Generic context values 20 | Suspend = 'suspend', 21 | NotSuspend = 'notSuspend', 22 | } 23 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-gitops-configure-webapp", 3 | "version": "0.5.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vite", 7 | "build": "vite build", 8 | "serve": "vite preview", 9 | "watch": "vite build --watch" 10 | }, 11 | "dependencies": { 12 | "@vscode/codicons": "^0.0.32", 13 | "@vscode/webview-ui-toolkit": "^1.0.0", 14 | "solid-collapse": "^1.0.0", 15 | "solid-js": "^1.3.13", 16 | "vscode-gitops-configure-webapp": "file:..", 17 | "vscode-gitops-tools": "file:.." 18 | }, 19 | "devDependencies": { 20 | "@types/vscode-webview": "^1.57.0", 21 | "typescript": "^4.8.4", 22 | "vite": "^2.9.13", 23 | "vite-plugin-solid": "^2.2.6", 24 | "vite-tsconfig-paths": "^4.0.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/webview-backend/types.ts: -------------------------------------------------------------------------------- 1 | import { GitInfo } from '../git/gitInfo'; 2 | import { GitOpsTemplate, TemplateParam } from '../kubernetes/types/flux/gitOpsTemplate'; 3 | import { ClusterInfo, KubernetesObject } from '../kubernetes/types/kubernetesTypes'; 4 | 5 | export type ConfigureGitOpsWebviewParams = { 6 | clusterInfo: ClusterInfo; 7 | gitInfo: GitInfo | undefined; 8 | namespaces: string[]; 9 | sources: KubernetesObject[]; 10 | selectSourceTab: boolean; 11 | selectedSource: string; 12 | set: any; 13 | }; 14 | 15 | export type CreateFromTemplateWebviewParams = { 16 | name?: string; 17 | namespace?: string; 18 | description?: string; 19 | folder: string; 20 | params: TemplateParam[]; 21 | }; 22 | 23 | export type WebviewParams = ConfigureGitOpsWebviewParams | CreateFromTemplateWebviewParams; 24 | 25 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/Azure.tsx: -------------------------------------------------------------------------------- 1 | import ListSelect from 'components/Common/ListSelect'; 2 | import Checkbox from 'components/Common/Checkbox'; 3 | 4 | function Azure() { 5 | return ( 6 |
7 |
8 | 9 | Create with FluxConfig 10 | 11 |
A new Azure FluxConfig resource will be created to manage created resources
12 |
13 |
14 | 15 |
16 | ['cluster', 'namespace']}/> 19 |
20 |
21 |
22 | ); 23 | } 24 | 25 | export default Azure; 26 | -------------------------------------------------------------------------------- /src/views/dataProviders/templateDataProvider.ts: -------------------------------------------------------------------------------- 1 | import { kubernetesTools } from '../../kubernetes/kubernetesTools'; 2 | import { sortByMetadataName } from '../../kubernetes/kubernetesUtils'; 3 | import { GitOpsTemplate } from '../../kubernetes/types/flux/gitOpsTemplate'; 4 | import { GitOpsTemplateNode } from '../nodes/gitOpsTemplateNode'; 5 | import { DataProvider } from './dataProvider'; 6 | 7 | export class TemplateDataProvider extends DataProvider { 8 | 9 | async buildTree(): Promise { 10 | const nodes = []; 11 | 12 | const templates = await kubernetesTools.getGitOpsTemplates(); 13 | 14 | if(templates) { 15 | for (const template of sortByMetadataName(templates.items)) { 16 | nodes.push(new GitOpsTemplateNode(template)); 17 | } 18 | } 19 | 20 | return nodes; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/lib/utils/debug.ts: -------------------------------------------------------------------------------- 1 | import { setGitOpsTemplate, GitOpsTemplate } from 'lib/model'; 2 | 3 | export function debug(str: string) { 4 | const e = document.getElementById('debug'); 5 | if(e) { 6 | e.innerHTML = `${e.innerHTML}\n${str}`; 7 | // e.style.display = 'block'; 8 | e.scrollTop = e.scrollHeight; 9 | } 10 | } 11 | 12 | export function debugStandalone() { 13 | const debugParams: GitOpsTemplate = { 14 | name: 'Template 001', 15 | description: 'Template001 description', 16 | folder: '/tmp', 17 | params: [{ 18 | name: 'p1', 19 | options: ['o1', 'o2', 'o3'], 20 | default: 'o2', 21 | }, 22 | { 23 | name: 'p2', 24 | default: 'defaultval', 25 | }], 26 | }; 27 | 28 | 29 | setTimeout(() => { 30 | setGitOpsTemplate(debugParams); 31 | }, 300); 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/fluxReconcileGitRepositoryForPath.ts: -------------------------------------------------------------------------------- 1 | import { Uri, window } from 'vscode'; 2 | import { fluxTools } from '../flux/fluxTools'; 3 | import { getFolderGitInfo, getGitRepositoryforGitInfo } from '../git/gitInfo'; 4 | 5 | /** 6 | * Command to reconcile GitRepository for selected file 7 | */ 8 | export async function fluxReconcileRepositoryForPath(fileExplorerUri?: Uri) { 9 | if(!fileExplorerUri) { 10 | return; 11 | } 12 | 13 | const gitInfo = await getFolderGitInfo(fileExplorerUri.fsPath); 14 | const gr = await getGitRepositoryforGitInfo(gitInfo); 15 | 16 | if(!gr?.metadata?.name || !gr.metadata?.namespace) { 17 | window.showWarningMessage(`No GitRepository with url '${gitInfo?.url}'`); 18 | return; 19 | } 20 | 21 | await fluxTools.reconcile('source git', gr.metadata.name, gr.metadata.namespace); 22 | } 23 | -------------------------------------------------------------------------------- /src/extensionContext.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, Uri } from 'vscode'; 2 | 3 | let extensionContext: ExtensionContext; 4 | 5 | /** 6 | * Save a referece for this (GitOps) extension's context 7 | */ 8 | export function setExtensionContext(context: ExtensionContext) { 9 | extensionContext = context; 10 | } 11 | 12 | /** 13 | * Return a reference for this (GitOps) extension's context 14 | */ 15 | export function getExtensionContext(): ExtensionContext { 16 | return extensionContext; 17 | } 18 | 19 | /** 20 | * Transform relative path inside the extension folder 21 | * to absolute path. 22 | * @param relativePath relative path to the file 23 | * @returns Uri of the file 24 | */ 25 | export function asAbsolutePath(relativePath: string): Uri { 26 | return Uri.file(extensionContext.asAbsolutePath(relativePath)); 27 | } 28 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Common/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { createEffect, onMount } from 'solid-js'; 2 | import { Checkbox as FastCheckbox} from '@microsoft/fast-foundation'; 3 | import { storeAccessors } from 'lib/model'; 4 | import { ReactiveInputProps } from 'lib/types'; 5 | 6 | function Checkbox(props: ReactiveInputProps) { 7 | let checkboxElement: FastCheckbox; 8 | 9 | const {get, set} = storeAccessors(props); 10 | 11 | onMount(() => { 12 | createEffect(() => checkboxElement.checked = get()); 13 | 14 | checkboxElement.addEventListener('change', (e: Event) => { 15 | set(checkboxElement.checked); 16 | }); 17 | }); 18 | 19 | return ( 20 | 21 | {props.children} 22 | 23 | ); 24 | } 25 | 26 | export default Checkbox; 27 | -------------------------------------------------------------------------------- /src/views/nodes/bucketNode.ts: -------------------------------------------------------------------------------- 1 | import { Bucket } from '../../kubernetes/types/flux/bucket'; 2 | import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 3 | import { SourceNode } from './sourceNode'; 4 | 5 | /** 6 | * Defines Bucket tree view item for display in GitOps Sources tree view. 7 | */ 8 | export class BucketNode extends SourceNode { 9 | 10 | /** 11 | * Bucket kubernetes resource object 12 | */ 13 | resource: Bucket; 14 | 15 | /** 16 | * Creates new bucket tree view item for display. 17 | * @param bucket Bucket kubernetes object info. 18 | */ 19 | constructor(bucket: Bucket) { 20 | super(`${KubernetesObjectKinds.Bucket}: ${bucket.metadata?.name}`, bucket); 21 | 22 | this.resource = bucket; 23 | } 24 | 25 | get contexts() { 26 | return [KubernetesObjectKinds.Bucket]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/flux/cliArgs.ts: -------------------------------------------------------------------------------- 1 | import { paramCase } from 'change-case'; 2 | import shell from 'shell-escape-tag'; 3 | 4 | export function buildCLIArgs(data: any): string { 5 | let cli = '--timeout=5s'; 6 | for (const [k, v] of Object.entries(data)) { 7 | if(v === '' || v === false) { 8 | continue; 9 | } 10 | 11 | const paramName = paramCase(k); 12 | if(v === true) { 13 | cli += ` --${paramName}`; 14 | } else { 15 | cli += shell` --${paramName}=${v}`; 16 | } 17 | } 18 | 19 | return cli; 20 | } 21 | 22 | export function cliKind(kind: string): string { 23 | switch(kind) { 24 | case 'GitRepository': 25 | return 'git'; 26 | case 'OCIRepository': 27 | return 'oci'; 28 | case 'HelmRepository': 29 | return 'helm'; 30 | case 'Bucket': 31 | return 'bucker'; 32 | default: 33 | return 'git'; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/HelmRepository.tsx: -------------------------------------------------------------------------------- 1 | 2 | import TextInput from 'components/Common/TextInput'; 3 | import { helmRepository, source } from '../../../lib/model'; 4 | import Name from './Common/Name'; 5 | import Namespace from './Common/Namespace'; 6 | 7 | import SettingsPanel from './Settings/HelmRepository/Panel'; 8 | import HelmConnection from './Settings/HelmRepository/HelmConnection'; 9 | 10 | 11 | export const isOCIHelm = () => helmRepository.url.indexOf('oci://') === 0; 12 | 13 | function HelmRepository() { 14 | return ( 15 |
16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 | 24 |
25 | ); 26 | } 27 | 28 | export default HelmRepository; 29 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/OCIRepository/OCITLS.tsx: -------------------------------------------------------------------------------- 1 | import Checkbox from 'components/Common/Checkbox'; 2 | import { ToolkitHelpLink } from 'components/Common/HelpLink'; 3 | import TextInput from 'components/Common/TextInput'; 4 | 5 | 6 | function OCITLS() { 7 | return ( 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | Allow insecure (non-TLS) connection to the registry 16 | 17 |
18 |
19 | ); 20 | } 21 | 22 | export default OCITLS; 23 | -------------------------------------------------------------------------------- /src/views/dataProviders/documentationDataProvider.ts: -------------------------------------------------------------------------------- 1 | import { documentationLinks } from '../documentationConfig'; 2 | import { DocumentationNode } from '../nodes/documentationNode'; 3 | import { DataProvider } from './dataProvider'; 4 | 5 | /** 6 | * Defines data provider for Documentation tree view. 7 | */ 8 | export class DocumentationDataProvider extends DataProvider { 9 | 10 | /** 11 | * Creates documentation tree view from documenation links config. 12 | */ 13 | async buildTree(): Promise { 14 | const treeNodes: DocumentationNode[] = []; 15 | 16 | for (const link of documentationLinks) { 17 | const treeNode = new DocumentationNode(link, true); 18 | 19 | for (const childLink of link.links || []) { 20 | treeNode.addChild(new DocumentationNode(childLink, false)); 21 | } 22 | 23 | treeNodes.push(treeNode); 24 | } 25 | 26 | return treeNodes; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/components/Common/ParamTextInput.tsx: -------------------------------------------------------------------------------- 1 | import { TemplateParam, values } from 'lib/model'; 2 | import { onMount } from 'solid-js'; 3 | import { RequiredWarning } from './RequiredWarning'; 4 | 5 | 6 | function ParamInput(props: { param: TemplateParam; }) { 7 | const param = props.param; 8 | const name = param.name; 9 | 10 | let inputElement: HTMLInputElement; 11 | onMount(() => { 12 | values.set(name, inputElement.value); 13 | 14 | inputElement.addEventListener('input', (e: Event) => values.set(name, inputElement.value)); 15 | }); 16 | 17 | return ( 18 |
19 |
20 | 21 | {param.description} 22 |
23 | ); 24 | } 25 | 26 | export default ParamInput; 27 | 28 | export {}; 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": [ 10 | "$ts-webpack-watch", 11 | "$tslint-webpack-watch" 12 | ], 13 | "isBackground": true, 14 | "presentation": { 15 | "reveal": "never", 16 | "group": "watchers" 17 | }, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "watch-tests", 26 | "problemMatcher": "$tsc-watch", 27 | "isBackground": true, 28 | "presentation": { 29 | "reveal": "never", 30 | "group": "watchers" 31 | }, 32 | "group": "build" 33 | }, 34 | { 35 | "label": "tasks: watch-tests", 36 | "dependsOn": [ 37 | "npm: watch", 38 | "npm: watch-tests" 39 | ], 40 | "problemMatcher": [] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /src/commands/addKustomization.ts: -------------------------------------------------------------------------------- 1 | import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; 2 | import { SourceNode } from '../views/nodes/sourceNode'; 3 | import { FluxSourceKinds } from '../kubernetes/types/flux/object'; 4 | 5 | /** 6 | * Open ConfigureGitops webview with a source preselected (if user right-clicked a source node) 7 | * @param sourceNode user right-clicked this in the Sources treeview 8 | */ 9 | export async function addKustomization(sourceNode?: SourceNode) { 10 | let sourceObject = sourceNode?.resource; 11 | 12 | // when Workloads '+' button is clicked (instead of right-clicking a Source treeview item) 13 | // sourceNode resource will be the first object in Workloads view (for example an Kustomization) 14 | // the type checker does not know this 15 | if(sourceObject?.kind && !FluxSourceKinds.includes(sourceObject?.kind)) { 16 | sourceObject = undefined; 17 | } 18 | 19 | openConfigureGitOpsWebview(true, sourceObject); 20 | } 21 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import Mocha from 'mocha'; 3 | import glob from 'glob'; 4 | 5 | // correct stack traces for assertions 6 | import 'source-map-support/register'; 7 | 8 | export function run(): Promise { 9 | // Create the mocha test 10 | const mocha = new Mocha({ 11 | ui: 'tdd', 12 | color: true, 13 | }); 14 | 15 | const testsRoot = path.resolve(__dirname, '..'); 16 | 17 | return new Promise((c, e) => { 18 | glob('**/**.test.js', { cwd: testsRoot }, (err: any, files: any[]) => { 19 | if (err) { 20 | return e(err); 21 | } 22 | 23 | // Add files to the test suite 24 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 25 | 26 | try { 27 | // Run the mocha test 28 | mocha.run(failures => { 29 | if (failures > 0) { 30 | e(new Error(`${failures} tests failed.`)); 31 | } else { 32 | c(); 33 | } 34 | }); 35 | } catch (error) { 36 | e(error); 37 | } 38 | }); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /src/webview-backend/configureGitOps/actions.ts: -------------------------------------------------------------------------------- 1 | import { ParamsDictionary } from '../../utils/typeUtils'; 2 | import { createConfigurationAzure } from './lib/createAzure'; 3 | import { createConfigurationGeneric } from './lib/createGeneric'; 4 | import { exportConfigurationGeneric } from './lib/exportGeneric'; 5 | 6 | 7 | const isAzure = (data: ParamsDictionary) => data.clusterInfo.isAzure && data.source?.createFluxConfig; 8 | 9 | function removeAzureData(data: any) { 10 | if(data.source) { 11 | delete data.source.createFluxConfig; 12 | delete data.source.azureScope; 13 | } 14 | } 15 | 16 | export async function actionCreate(data: ParamsDictionary) { 17 | if(isAzure(data)) { 18 | createConfigurationAzure(data); 19 | } else { 20 | removeAzureData(data); 21 | createConfigurationGeneric(data); 22 | } 23 | } 24 | 25 | 26 | export async function actionYAML(data: ParamsDictionary) { 27 | if(!isAzure(data)) { 28 | removeAzureData(data); 29 | exportConfigurationGeneric(data); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/vscodeContext.ts: -------------------------------------------------------------------------------- 1 | import { commands } from 'vscode'; 2 | import { CommandId } from './commands'; 3 | 4 | /** 5 | * GitOps context types. 6 | */ 7 | export const enum ContextTypes { 8 | NoClusterSelected = 'gitops:noClusterSelected', 9 | CurrentClusterGitOpsNotEnabled = 'gitops:currentClusterGitOpsNotEnabled', 10 | 11 | LoadingClusters = 'gitops:loadingClusters', 12 | LoadingSources = 'gitops:loadingSources', 13 | LoadingWorkloads = 'gitops:loadingWorkloads', 14 | 15 | FailedToLoadClusterContexts = 'gitops:failedToLoadClusterContexts', 16 | NoClusters = 'gitops:noClusters', 17 | NoSources = 'gitops:noSources', 18 | NoWorkloads = 'gitops:noWorkloads', 19 | 20 | IsDev = 'gitops:isDev', 21 | IsWGE = 'gitops:isWGE', 22 | } 23 | 24 | 25 | /** 26 | * Type-safe way to set context for future use in: 27 | * menus, keybindings, welcomeView... 28 | */ 29 | export async function setVSCodeContext(context: ContextTypes, value: boolean) { 30 | return await commands.executeCommand(CommandId.VSCodeSetContext, context, value); 31 | } 32 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/components/Common/ParamSelect.tsx: -------------------------------------------------------------------------------- 1 | import { TemplateParam, values } from 'lib/model'; 2 | import { For, onMount } from 'solid-js'; 3 | import { RequiredWarning } from './RequiredWarning'; 4 | 5 | export function ParamSelect(props: {param: TemplateParam;}) { 6 | const param = props.param; 7 | 8 | let selectElement: HTMLSelectElement; 9 | 10 | onMount(() => { 11 | values.set(param.name, selectElement.value); 12 | 13 | selectElement.addEventListener('change', (e: Event) => { 14 | values.set(param.name, selectElement.value); 15 | }); 16 | }); 17 | 18 | return ( 19 |
20 |
21 | 28 |
{param.description} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/lib/types.d.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // type StoreAccessor = { 5 | // store?: 'source'; 6 | // field?: keyof (typeof source); 7 | // } | { 8 | // store: 'gitRepository'; 9 | // field: keyof (typeof gitRepository); 10 | // } | { 11 | // store: 'helmRepository'; 12 | // field: keyof (typeof helmRepository); 13 | // } | { 14 | // store: 'ociRepository'; 15 | // field: keyof (typeof ociRepository); 16 | // } | { 17 | // store: 'bucket'; 18 | // field: keyof (typeof bucket); 19 | // } | { 20 | // store: 'kustomization'; 21 | // field: keyof (typeof kustomization); 22 | // }; 23 | 24 | 25 | // type InputProps = { 26 | // class?: string; 27 | // style?: string; 28 | // type?: string; 29 | // }; 30 | 31 | // type Children = { 32 | // children?: any; 33 | // } 34 | 35 | // type ListItemProps = { 36 | // items?: any; 37 | // changed?: (a: any, b: any) => any; 38 | // } 39 | 40 | type ExplicitGetSet = { 41 | get?: () => any; 42 | set?: (v: any) => any; 43 | }; 44 | 45 | 46 | export type ReactiveInputProps = ExplicitGetSet; 47 | -------------------------------------------------------------------------------- /src/kubernetes/types/flux/object.ts: -------------------------------------------------------------------------------- 1 | import { FluxWorkload } from '../../../flux/fluxTypes'; 2 | import { KubernetesObjectKinds } from '../kubernetesTypes'; 3 | import { Bucket } from './bucket'; 4 | import { GitRepository } from './gitRepository'; 5 | import { HelmRelease } from './helmRelease'; 6 | import { HelmRepository } from './helmRepository'; 7 | import { Kustomize } from './kustomize'; 8 | import { OCIRepository } from './ociRepository'; 9 | 10 | export type FluxSourceObject = GitRepository | OCIRepository | HelmRepository | Bucket; 11 | export type FluxWorkloadObject = Kustomize | HelmRelease; 12 | 13 | export const FluxSourceKinds: string[] = [ 14 | KubernetesObjectKinds.GitRepository, 15 | KubernetesObjectKinds.OCIRepository, 16 | KubernetesObjectKinds.HelmRepository, 17 | KubernetesObjectKinds.Bucket, 18 | ]; 19 | 20 | 21 | export function namespacedObject(resource?: FluxSourceObject | FluxWorkloadObject): string | undefined { 22 | if(resource) { 23 | return `${resource.kind}/${resource.metadata.name}.${resource.metadata.namespace}`; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/kubernetes/types/flux/gitOpsTemplate.ts: -------------------------------------------------------------------------------- 1 | import { KubernetesObject, KubernetesObjectKinds, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; 2 | 3 | /** 4 | * `kubectl get GitOpsTemplate -A` command. 5 | */ 6 | export interface GitOpsTemplateResult { 7 | readonly apiVersion: string; 8 | readonly kind: KubernetesObjectKinds.List; 9 | readonly items: GitOpsTemplate[]; 10 | readonly metadata: ResultMetadata; 11 | } 12 | 13 | 14 | export interface GitOpsTemplate extends KubernetesObject { 15 | // standard kubernetes object fields 16 | readonly apiVersion: string; 17 | readonly kind: KubernetesObjectKinds.GitOpsTemplate; 18 | readonly metadata: ObjectMeta; 19 | 20 | readonly spec: { 21 | readonly params?: TemplateParam[]; 22 | readonly description?: string; 23 | }; 24 | 25 | readonly status?: any; 26 | } 27 | 28 | /** 29 | * params spec for GitOpsTempalte 30 | */ 31 | export interface TemplateParam { 32 | readonly name: string; 33 | readonly description: string; 34 | readonly options?: string[]; 35 | readonly default?: string; 36 | readonly required?: boolean; 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/types.d.ts: -------------------------------------------------------------------------------- 1 | import { bucket, gitRepository, helmRepository, kustomization, ociRepository, source } from "./model"; 2 | 3 | type ExplicitGetSet = { 4 | get?: () => any; 5 | set?: (v: any) => any; 6 | }; 7 | 8 | 9 | type StoreAccessor = { 10 | store?: 'source'; 11 | field?: keyof (typeof source); 12 | } | { 13 | store: 'gitRepository'; 14 | field: keyof (typeof gitRepository); 15 | } | { 16 | store: 'helmRepository'; 17 | field: keyof (typeof helmRepository); 18 | } | { 19 | store: 'ociRepository'; 20 | field: keyof (typeof ociRepository); 21 | } | { 22 | store: 'bucket'; 23 | field: keyof (typeof bucket); 24 | } | { 25 | store: 'kustomization'; 26 | field: keyof (typeof kustomization); 27 | }; 28 | 29 | 30 | type InputProps = { 31 | class?: string; 32 | style?: string; 33 | type?: string; 34 | }; 35 | 36 | type Children = { 37 | children?: any; 38 | } 39 | 40 | type ListItemProps = { 41 | items?: any; 42 | changed?: (a: any, b: any) => any; 43 | } 44 | 45 | 46 | export type ReactiveInputProps = StoreAccessor & ExplicitGetSet & InputProps & ListItemProps & Children; 47 | -------------------------------------------------------------------------------- /src/webview-backend/configureGitOps/lib/createGeneric.ts: -------------------------------------------------------------------------------- 1 | import { showDeployKeyNotificationIfNeeded } from '../../../commands/createSource'; 2 | import { telemetry } from '../../../extension'; 3 | import { fluxTools } from '../../../flux/fluxTools'; 4 | import { TelemetryEventNames } from '../../../telemetry'; 5 | import { ParamsDictionary } from '../../../utils/typeUtils'; 6 | import { refreshAllTreeViews, refreshSourcesTreeView } from '../../../views/treeViews'; 7 | 8 | export async function createConfigurationGeneric(data: ParamsDictionary) { 9 | telemetry.send(TelemetryEventNames.CreateSource, { 10 | kind: data.source?.kind, 11 | }); 12 | 13 | 14 | if(data.source) { 15 | const deployKey = await fluxTools.createSource(data.source); 16 | showDeployKeyNotificationIfNeeded(data.source.url, deployKey); 17 | setTimeout(() => { 18 | // Wait a bit for the repository to have a failed state in case of SSH url 19 | refreshSourcesTreeView(); 20 | }, 1000); 21 | 22 | } 23 | 24 | if(data.kustomization) { 25 | await fluxTools.createKustomization(data.kustomization); 26 | } 27 | 28 | refreshAllTreeViews(); 29 | } 30 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Common/ListSelect.tsx: -------------------------------------------------------------------------------- 1 | import { storeAccessors } from 'lib/model'; 2 | import { ReactiveInputProps } from 'lib/types'; 3 | import { For, onMount } from 'solid-js'; 4 | 5 | function ListSelect(props: ReactiveInputProps) { 6 | // use 28 | {(name: string) => 29 | 30 | } 31 | 32 | 33 | ); 34 | } 35 | 36 | export default ListSelect; 37 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/GitRepository.tsx: -------------------------------------------------------------------------------- 1 | import ListSelect from 'components/Common/ListSelect'; 2 | import TextInput from 'components/Common/TextInput'; 3 | 4 | import SettingsPanel from './Settings/GitRepository/Panel'; 5 | import Name from './Common/Name'; 6 | import Namespace from './Common/Namespace'; 7 | import GitConnection from './Settings/GitRepository/GitConnection'; 8 | 9 | function GitRepository() { 10 | return ( 11 |
12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 | 20 |
21 | 22 |
23 | ['branch', 'tag', 'semver']}/> 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 |
34 | ); 35 | } 36 | 37 | export default GitRepository; 38 | -------------------------------------------------------------------------------- /src/views/nodes/kustomizationNode.ts: -------------------------------------------------------------------------------- 1 | import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 2 | import { Kustomize } from '../../kubernetes/types/flux/kustomize'; 3 | import { NodeContext } from './nodeContext'; 4 | import { WorkloadNode } from './workloadNode'; 5 | 6 | /** 7 | * Defines Kustomization tree view item for display in GitOps Workload tree view. 8 | */ 9 | export class KustomizationNode extends WorkloadNode { 10 | /** 11 | * Kustomize kubernetes resource object 12 | */ 13 | resource: Kustomize; 14 | 15 | /** 16 | * Creates new app kustomization tree view item for display. 17 | * @param kustomization Kustomize kubernetes object info. 18 | */ 19 | constructor(kustomization: Kustomize) { 20 | super(kustomization.metadata?.name || '', kustomization); 21 | 22 | this.resource = kustomization; 23 | 24 | this.makeCollapsible(); 25 | } 26 | 27 | get contexts() { 28 | const contextsArr: string[] = [KubernetesObjectKinds.Kustomization]; 29 | contextsArr.push( 30 | this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, 31 | ); 32 | return contextsArr; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/views/nodes/helmReleaseNode.ts: -------------------------------------------------------------------------------- 1 | import { HelmRelease } from '../../kubernetes/types/flux/helmRelease'; 2 | import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 3 | import { NodeContext } from './nodeContext'; 4 | import { WorkloadNode } from './workloadNode'; 5 | 6 | /** 7 | * Defines Helm release tree view item for display in GitOps Workloads tree view. 8 | */ 9 | export class HelmReleaseNode extends WorkloadNode { 10 | 11 | /** 12 | * Helm release kubernetes resource object 13 | */ 14 | resource: HelmRelease; 15 | 16 | /** 17 | * Creates new helm release tree view item for display. 18 | * @param helmRelease Helm release kubernetes object info. 19 | */ 20 | constructor(helmRelease: HelmRelease) { 21 | super(helmRelease.metadata?.name || '', helmRelease); 22 | 23 | this.resource = helmRelease; 24 | 25 | this.makeCollapsible(); 26 | 27 | } 28 | 29 | get contexts() { 30 | const contextsArr: string[] = [KubernetesObjectKinds.HelmRelease]; 31 | contextsArr.push( 32 | this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, 33 | ); 34 | return contextsArr; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/OCIRepository/Panel.tsx: -------------------------------------------------------------------------------- 1 | import TextInput from 'components/Common/TextInput'; 2 | import { Collapse } from 'solid-collapse'; 3 | import { createSignal } from 'solid-js'; 4 | import SettingsIntervals from '../Intervals'; 5 | import SettingsTLS from './OCITLS'; 6 | 7 | function Panel() { 8 | const [isOpen, setIsOpen] = createSignal(false); 9 | 10 | return ( 11 |
12 |

setIsOpen(!isOpen())}> Advanced Settings

14 | 15 |
16 | 17 | 18 |
19 | 20 | 21 |
22 |
23 |
24 |
25 | ); 26 | } 27 | 28 | export default Panel; 29 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "npm: watch", 19 | "sourceMaps": true 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}", 27 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 28 | ], 29 | "outFiles": [ 30 | "${workspaceFolder}/out/**/*.js", 31 | "${workspaceFolder}/dist/**/*.js" 32 | ], 33 | "preLaunchTask": "tasks: watch-tests", 34 | "sourceMaps": true 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /src/views/nodes/gitRepositoryNode.ts: -------------------------------------------------------------------------------- 1 | import { GitRepository } from '../../kubernetes/types/flux/gitRepository'; 2 | import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 3 | import { NodeContext } from './nodeContext'; 4 | import { SourceNode } from './sourceNode'; 5 | 6 | /** 7 | * Defines GitRepository tree view item for display in GitOps Sources tree view. 8 | */ 9 | export class GitRepositoryNode extends SourceNode { 10 | 11 | /** 12 | * Git repository kubernetes resource object 13 | */ 14 | resource: GitRepository; 15 | 16 | /** 17 | * Creates new git repository tree view item for display. 18 | * @param gitRepository Git repository kubernetes object info. 19 | */ 20 | constructor(gitRepository: GitRepository) { 21 | super(`${KubernetesObjectKinds.GitRepository}: ${gitRepository.metadata?.name}`, gitRepository); 22 | 23 | this.resource = gitRepository; 24 | } 25 | 26 | get contexts() { 27 | const contextsArr: string[] = [KubernetesObjectKinds.GitRepository]; 28 | contextsArr.push( 29 | this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, 30 | ); 31 | return contextsArr; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/views/nodes/ociRepositoryNode.ts: -------------------------------------------------------------------------------- 1 | import { OCIRepository } from '../../kubernetes/types/flux/ociRepository'; 2 | import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 3 | import { NodeContext } from './nodeContext'; 4 | import { SourceNode } from './sourceNode'; 5 | 6 | /** 7 | * Defines OCIRepository tree view item for display in GitOps Sources tree view. 8 | */ 9 | export class OCIRepositoryNode extends SourceNode { 10 | 11 | /** 12 | * OCI repository kubernetes resource object 13 | */ 14 | resource: OCIRepository; 15 | 16 | /** 17 | * Creates new oci repository tree view item for display. 18 | * @param ociRepository OCI repository kubernetes object info. 19 | */ 20 | constructor(ociRepository: OCIRepository) { 21 | super(`${KubernetesObjectKinds.OCIRepository}: ${ociRepository.metadata?.name}`, ociRepository); 22 | 23 | this.resource = ociRepository; 24 | } 25 | 26 | get contexts() { 27 | const contextsArr: string[] = [KubernetesObjectKinds.OCIRepository]; 28 | contextsArr.push( 29 | this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, 30 | ); 31 | return contextsArr; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/scripts/updateEdgeVersion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Currently edge releases have the following format: `3.0.0-edge.1` which 3 | * is valid semver but invalid as version to be published on the marketplace 4 | * (see also https://github.com/microsoft/vscode-vsce/issues/148 for context). 5 | * This means that edge releases are currently not possible with the workflow 6 | * we have. 7 | */ 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | const pkgPath = path.join(__dirname, '..', '..', 'package.json'); 12 | const pkg = JSON.parse(fs.readFileSync(pkgPath).toString()); 13 | 14 | const newVersion = pkg.version.split('.').slice(0, 2) 15 | 16 | /** 17 | * VSCode Marketplace version requirements: 18 | * It must be one to four numbers in the range 0 to 2147483647, 19 | * with each number seperated by a period. It must contain at least one non-zero number. 20 | */ 21 | const prereleaseDate = Math.floor(Date.now() / 1000); 22 | newVersion.push(prereleaseDate); 23 | pkg.version = `${newVersion.join('.')}`; 24 | 25 | console.log(`Update package.json with Edge version:\n\n${JSON.stringify(pkg, null, 2)}`); 26 | fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)) 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [ main, integration-tests, edge ] 6 | pull_request: 7 | branches: [ main, edge ] 8 | 9 | jobs: 10 | e2e-testing: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3 15 | 16 | - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # pin@v3 17 | with: 18 | node-version: '20' 19 | 20 | - run: npm install 21 | 22 | - name: Setup Kubernetes 23 | uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 # pin@v0.5.0 24 | with: 25 | version: v0.20.0 26 | image: kindest/node:v1.28.0 27 | 28 | - name: Setup Flux CLI 29 | uses: fluxcd/flux2/action@1730f3c46bddf0a29787d8d4fa5ace283f298e49 # pin@main 30 | with: 31 | token: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: extension test 34 | uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # pin@v1 35 | with: 36 | run: 'npm test' 37 | options: "-screen 0 1600x1200x24" 38 | -------------------------------------------------------------------------------- /src/views/nodes/helmRepositoryNode.ts: -------------------------------------------------------------------------------- 1 | import { HelmRepository } from '../../kubernetes/types/flux/helmRepository'; 2 | import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 3 | import { NodeContext } from './nodeContext'; 4 | import { SourceNode } from './sourceNode'; 5 | 6 | /** 7 | * Defines HelmRepository tree view item for display in GitOps Sources tree view. 8 | */ 9 | export class HelmRepositoryNode extends SourceNode { 10 | 11 | /** 12 | * Helm repository kubernetes resource object 13 | */ 14 | resource: HelmRepository; 15 | 16 | /** 17 | * Creates new helm repository tree view item for display. 18 | * @param helmRepository Helm repository kubernetes object info. 19 | */ 20 | constructor(helmRepository: HelmRepository) { 21 | super(`${KubernetesObjectKinds.HelmRepository}: ${helmRepository.metadata?.name}`, helmRepository); 22 | 23 | this.resource = helmRepository; 24 | } 25 | 26 | get contexts() { 27 | const contextsArr: string[] = [KubernetesObjectKinds.HelmRepository]; 28 | contextsArr.push( 29 | this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, 30 | ); 31 | return contextsArr; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/Bucket/Panel.tsx: -------------------------------------------------------------------------------- 1 | import Checkbox from 'components/Common/Checkbox'; 2 | import { params } from 'lib/params'; 3 | import { Collapse } from 'solid-collapse'; 4 | import { createSignal, Show } from 'solid-js'; 5 | import Azure from '../Azure'; 6 | import Intervals from '../Intervals'; 7 | 8 | function Panel() { 9 | const [isOpen, setIsOpen] = createSignal(false); 10 | 11 | return ( 12 |
13 |

setIsOpen(!isOpen())}> Advanced Settings

15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Allow insecure (non-TLS) connection to the repository 26 | 27 | 28 |
29 |
30 |
31 | ); 32 | } 33 | 34 | export default Panel; 35 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/HelmRepository/Panel.tsx: -------------------------------------------------------------------------------- 1 | import Checkbox from 'components/Common/Checkbox'; 2 | import { ToolkitHelpLink } from 'components/Common/HelpLink'; 3 | import { Collapse } from 'solid-collapse'; 4 | import { createSignal } from 'solid-js'; 5 | import Intervals from '../Intervals'; 6 | import Connection from './HelmConnection'; 7 | 8 | function Panel() { 9 | const [isOpen, setIsOpen] = createSignal(false); 10 | 11 | return ( 12 |
13 |

setIsOpen(!isOpen())}> Advanced Settings

15 | 16 |
17 | 18 | Pass credentials to all domains (HTTP/S repositories only) 19 | 20 | 21 | 22 |
23 | 24 |
25 |
26 | ); 27 | } 28 | 29 | export default Panel; 30 | -------------------------------------------------------------------------------- /src/webview-backend/configureGitOps/lib/exportGeneric.ts: -------------------------------------------------------------------------------- 1 | import { window, workspace } from 'vscode'; 2 | import { telemetry } from '../../../extension'; 3 | import { fluxTools } from '../../../flux/fluxTools'; 4 | import { TelemetryErrorEventNames, TelemetryEventNames } from '../../../telemetry'; 5 | import { ParamsDictionary } from '../../../utils/typeUtils'; 6 | 7 | export async function exportConfigurationGeneric(data: ParamsDictionary) { 8 | telemetry.send(TelemetryEventNames.ExportSource, { 9 | kind: data.source?.kind, 10 | }); 11 | 12 | let yaml = ''; 13 | 14 | if(data.source) { 15 | yaml += await fluxTools.exportSource(data.source); 16 | } 17 | 18 | if(data.kustomization) { 19 | yaml += await fluxTools.exportKustomization(data.kustomization); 20 | } 21 | 22 | showYaml(yaml); 23 | } 24 | 25 | 26 | async function showYaml(text: string) { 27 | return await workspace.openTextDocument({content: text} ).then(document => { 28 | if (document) { 29 | // document['languageId'] = 30 | window.showTextDocument(document); 31 | } 32 | }, 33 | error => { 34 | window.showErrorMessage(`Error loading document: ${error}`); 35 | telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_OPEN_RESOURCE); 36 | }); 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/nodes/gitOpsTemplateNode.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownString, ThemeColor, ThemeIcon } from 'vscode'; 2 | import { GitOpsTemplate } from '../../kubernetes/types/flux/gitOpsTemplate'; 3 | import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 4 | import { createMarkdownTable } from '../../utils/markdownUtils'; 5 | import { TreeNode } from './treeNode'; 6 | 7 | /** 8 | * Base class for all the Source tree view items. 9 | */ 10 | export class GitOpsTemplateNode extends TreeNode { 11 | resource: GitOpsTemplate; 12 | 13 | constructor(template: GitOpsTemplate) { 14 | super(template.metadata.name || 'No name'); 15 | 16 | this.resource = template; 17 | 18 | this.setIcon(new ThemeIcon('notebook-render-output', new ThemeColor('editorWidget.foreground'))); 19 | } 20 | 21 | get tooltip() { 22 | return this.getMarkdownHover(this.resource); 23 | } 24 | 25 | // @ts-ignore 26 | get description() { 27 | // return 'Description'; 28 | return false; 29 | } 30 | 31 | getMarkdownHover(template: GitOpsTemplate): MarkdownString { 32 | const markdown: MarkdownString = createMarkdownTable(template); 33 | return markdown; 34 | } 35 | 36 | get contexts() { 37 | return [KubernetesObjectKinds.GitOpsTemplate]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/kubernetes/types/kubernetesConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Kubernetes config result from 3 | * running `kubectl config view` command. 4 | */ 5 | export interface KubernetesConfig { 6 | readonly apiVersion: string; 7 | readonly 'current-context': string; 8 | readonly clusters: KubernetesCluster[] | undefined; 9 | readonly contexts: KubernetesContext[] | undefined; 10 | readonly users: { 11 | readonly name: string; 12 | readonly user: Record; 13 | }[] | undefined; 14 | } 15 | 16 | /** 17 | * Cluster info object. 18 | */ 19 | export interface KubernetesCluster { 20 | readonly name: string; 21 | readonly cluster: { 22 | readonly server: string; 23 | readonly 'certificate-authority'?: string; 24 | readonly 'certificate-authority-data'?: string; 25 | }; 26 | } 27 | 28 | /** 29 | * Context info object. 30 | */ 31 | export interface KubernetesContext { 32 | readonly name: string; 33 | readonly context: { 34 | readonly cluster: string; 35 | readonly user: string; 36 | readonly namespace?: string; 37 | }; 38 | } 39 | 40 | /** 41 | * Kubernetes context but with cluster object inside it. 42 | */ 43 | export type KubernetesContextWithCluster = KubernetesContext & { 44 | context: { 45 | clusterInfo?: KubernetesCluster; 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /src/views/nodes/documentationNode.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from 'vscode'; 2 | import { CommandId } from '../../commands'; 3 | import { asAbsolutePath } from '../../extensionContext'; 4 | import { DocumentationLink } from '../documentationConfig'; 5 | import { TreeNode } from './treeNode'; 6 | 7 | /** 8 | * Node for Documentation Tree View. 9 | */ 10 | export class DocumentationNode extends TreeNode { 11 | 12 | /** 13 | * Url of the external web page with documentation. 14 | */ 15 | url?: string; 16 | title: string; 17 | newUserGuide?: boolean; 18 | 19 | constructor(link: DocumentationLink, isParent = false) { 20 | super(link.title); 21 | 22 | this.title = link.title; 23 | this.newUserGuide = link.newUserGuide; 24 | 25 | if(link.url) { 26 | this.url = link.url; 27 | } 28 | 29 | if (link.icon) { 30 | this.iconPath = asAbsolutePath(link.icon); 31 | } 32 | } 33 | 34 | get tooltip() { 35 | return this.url || this.title; 36 | } 37 | 38 | get command() { 39 | if(this.url) { 40 | return { 41 | command: CommandId.VSCodeOpen, 42 | arguments: [Uri.parse(this.url)], 43 | title: 'Open link', 44 | }; 45 | } else if(this.newUserGuide) { 46 | return { 47 | command: CommandId.ShowNewUserGuide, 48 | title: 'Open link', 49 | }; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/webview-backend/createFromTemplate/receiveMessage.ts: -------------------------------------------------------------------------------- 1 | import { Uri, WebviewPanel, workspace } from 'vscode'; 2 | import { shell } from '../../shell'; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | 5 | 6 | export async function receiveMessage(message: any, panel: WebviewPanel) { 7 | switch (message.action) { 8 | case 'show-yaml': 9 | // actionYAML(message.data); 10 | console.log(message.data); 11 | const data = message.data; 12 | 13 | renderTemplates(data.template, data.values); 14 | panel.dispose(); 15 | return; 16 | } 17 | } 18 | 19 | 20 | async function renderTemplates(template: Record, values: Record) { 21 | const valuesArg = Object.entries(values).map(e => `${e[0]}=${e[1]}`).join(','); 22 | 23 | // download template 24 | const fname = `${template.folder}/${template.name}-${uuidv4()}.yaml`; 25 | 26 | const commandFetch = `kubectl get gitopstemplate ${template.name} -n ${template.namespace} -o yaml > ${fname}`; 27 | const commandRender = `gitops create template --template-file '${fname}' --output-dir ${template.folder} --values ${valuesArg}`; 28 | try { 29 | await shell.execWithOutput(commandFetch); 30 | const shellResult = await shell.execWithOutput(commandRender); 31 | } finally { 32 | workspace.fs.delete(Uri.file(fname)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/webview-backend/README.md: -------------------------------------------------------------------------------- 1 | # `webview-backend` Directory 2 | 3 | Types of content that can be contained here: 4 | 5 | Individual JavaScript / TypeScript files that contain a class which manages the state and behavior of a given webview panel. Each class is usually in charge of: 6 | 7 | - Creating and rendering the webview panel 8 | - Properly cleaning up and disposing of webview resources when the panel is closed 9 | - Setting message listeners so data can be passed between the webview and extension 10 | - Setting the HTML (and by proxy CSS/JavaScript) content of the webview panel 11 | - Other custom logic and behavior related to webview panel management 12 | 13 | 14 | ## `configureGitOps` Directory 15 | 16 | This contains the application used to create Sources and Workloads. 17 | 18 | - `Panel` is the vscode abstraction for the webview tab in which it lives. It sends and receives messages from the `webapp-ui` 19 | - `openPanel` creates the Panel and provides it with the current environment information 20 | - `actions` module is for actions initiated by user in the webapp, for example when user a clicks a button that vscode must handle 21 | - `lib` is the business logic 22 | 23 | 24 | ## `createFromTemplate` Directory 25 | 26 | Contains integration for the web app to configure the GitOpsTemplate user selected in the Templates view -------------------------------------------------------------------------------- /src/commands/fluxReconcileWorkload.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { fluxTools } from '../flux/fluxTools'; 3 | import { FluxWorkload } from '../flux/fluxTypes'; 4 | import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; 5 | import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; 6 | import { KustomizationNode } from '../views/nodes/kustomizationNode'; 7 | import { refreshWorkloadsTreeView } from '../views/treeViews'; 8 | 9 | /** 10 | * Invoke flux reconcile of a specific workload. 11 | * @param workload Target workload tree view item. 12 | */ 13 | export async function fluxReconcileWorkload(workload: KustomizationNode | HelmReleaseNode): Promise { 14 | /** 15 | * Accepted workload names in flux: `kustomization`, `helmrelease`. 16 | * Can be checked with: `flux reconcile --help` 17 | */ 18 | const workloadType: FluxWorkload | 'unknown' = workload.resource.kind === KubernetesObjectKinds.Kustomization ? 'kustomization' : 19 | workload.resource.kind === KubernetesObjectKinds.HelmRelease ? 'helmrelease' : 'unknown'; 20 | if (workloadType === 'unknown') { 21 | window.showErrorMessage(`Unknown Workload resource kind ${workload.resource.kind}`); 22 | return; 23 | } 24 | 25 | await fluxTools.reconcile(workloadType, workload.resource.metadata.name || '', workload.resource.metadata.namespace || ''); 26 | 27 | refreshWorkloadsTreeView(); 28 | } 29 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/GitRepository/Panel.tsx: -------------------------------------------------------------------------------- 1 | import { createSignal, Show } from 'solid-js'; 2 | import { Collapse } from 'solid-collapse'; 3 | 4 | import Azure from '../Azure'; 5 | import { params } from 'lib/params'; 6 | import SettingsIntervals from '../Intervals'; 7 | import Checkbox from 'components/Common/Checkbox'; 8 | 9 | function Panel() { 10 | const [isOpen, setIsOpen] = createSignal(false); 11 | 12 | return ( 13 |
14 |

setIsOpen(!isOpen())}> Advanced Settings

16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 | 24 | Recurse submodules 25 | 26 |
When enabled, configures the GitRepository source to initialize and include Git submodules in the artifact it produces
27 |
28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | ); 37 | } 38 | 39 | export default Panel; 40 | -------------------------------------------------------------------------------- /src/webview-backend/createFromTemplate/openWebview.ts: -------------------------------------------------------------------------------- 1 | import { Uri, window } from 'vscode'; 2 | import { getExtensionContext } from '../../extensionContext'; 3 | import { GitOpsTemplate } from '../../kubernetes/types/flux/gitOpsTemplate'; 4 | import { WebviewBackend } from '../WebviewBackend'; 5 | import { receiveMessage } from './receiveMessage'; 6 | 7 | let webview: WebviewBackend | undefined; 8 | 9 | 10 | export async function openCreateFromTemplatePanel(template: GitOpsTemplate) { 11 | if(!template.spec.params) { 12 | return; 13 | } 14 | 15 | const result = await window.showInformationMessage('Select folder for template results (Ex: fleet-infra/clusters)', {modal: true}, 'Ok'); 16 | if(result !== 'Ok') { 17 | return; 18 | } 19 | 20 | const folders = await window.showOpenDialog({title: 'Clusters manifests folder', canSelectFiles: false, canSelectFolders: true}); 21 | const folder = folders ? folders[0].fsPath : ''; 22 | const webviewParams = { 23 | name: template.metadata.name, 24 | namespace: template.metadata.namespace, 25 | description: template.spec.description, 26 | params: template.spec.params, 27 | folder, 28 | }; 29 | 30 | webview?.dispose(); 31 | 32 | const extensionUri = getExtensionContext().extensionUri; 33 | const uri = Uri.joinPath(extensionUri, 'webview-ui', 'createFromTemplate'); 34 | webview = new WebviewBackend('Create from Template', uri, webviewParams, receiveMessage); 35 | } 36 | -------------------------------------------------------------------------------- /src/commands/setClusterProvider.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { globalState } from '../extension'; 3 | import { ClusterMetadata } from '../globalState'; 4 | import { KnownClusterProviders, knownClusterProviders } from '../kubernetes/types/kubernetesTypes'; 5 | import { ClusterContextNode } from '../views/nodes/clusterContextNode'; 6 | import { refreshAllTreeViews } from '../views/treeViews'; 7 | 8 | export async function setClusterProvider(clusterNode: ClusterContextNode) { 9 | 10 | const automatically = 'Automatically (Let the extension infer)'; 11 | const quickPickItems: string[] = [...knownClusterProviders, automatically]; 12 | 13 | const pickedProvider = await window.showQuickPick(quickPickItems, { 14 | title: `Choose cluster provider for "${clusterNode.clusterName}" cluster.`, 15 | }); 16 | if (!pickedProvider) { 17 | return; 18 | } 19 | 20 | const clusterMetadata: ClusterMetadata = globalState.getClusterMetadata(clusterNode.clusterName) || {}; 21 | const oldClusterProvider = clusterMetadata.clusterProvider; 22 | 23 | if (pickedProvider === automatically) { 24 | clusterMetadata.clusterProvider = undefined; 25 | } else { 26 | clusterMetadata.clusterProvider = pickedProvider as KnownClusterProviders; 27 | } 28 | 29 | globalState.setClusterMetadata(clusterNode.clusterName, clusterMetadata); 30 | 31 | if (clusterMetadata.clusterProvider !== oldClusterProvider) { 32 | refreshAllTreeViews(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/flux/fluxUtils.ts: -------------------------------------------------------------------------------- 1 | import { sanitizeRFC1123 } from '../utils/stringUtils'; 2 | 3 | /** RegEx to validate the resource name (except the length of the string) */ 4 | const RFC1123SubdomainRegex = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/; 5 | 6 | /** 7 | * Validate Kustomization name using RFC 1123 subdomain. 8 | * - In Azure max combined length of source + kustomization is 62 chars 9 | * - In flux - the max length of kustomization is 253 chars 10 | */ 11 | export function validateKustomizationName(kustomizationName: string, gitRepositoryName = '', isAzure: boolean) { 12 | 13 | let nameToValidate; 14 | 15 | if (isAzure) { 16 | nameToValidate = `${sanitizeRFC1123(gitRepositoryName || '')}-${ kustomizationName}`; 17 | } else { 18 | nameToValidate = kustomizationName; 19 | } 20 | 21 | if (isAzure && nameToValidate.length > 62) { 22 | return 'Invalid value: The combined length of the kustomization name plus the configuration name cannot exceed 62 characters.'; 23 | } 24 | 25 | if (!isAzure && nameToValidate.length > 253) { 26 | return 'Invalid value: Maximum length is 253 characters.'; 27 | } 28 | 29 | if (RFC1123SubdomainRegex.test(kustomizationName)) { 30 | return ''; 31 | } else { 32 | return `Invalid value: "${kustomizationName}". A lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character.`; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/statusBar.ts: -------------------------------------------------------------------------------- 1 | import { StatusBarAlignment, StatusBarItem, window } from 'vscode'; 2 | 3 | class StatusBar { 4 | 5 | private statusBarItem: StatusBarItem; 6 | private statusBarItemName = 'gitops'; 7 | 8 | private numberOfLoadingTreeViews = 0; 9 | private loadingWasHidden = false; 10 | 11 | constructor() { 12 | this.statusBarItem = window.createStatusBarItem( 13 | this.statusBarItemName, 14 | StatusBarAlignment.Left, 15 | -1e10,// align to the right 16 | ); 17 | this.statusBarItem.text = '$(sync~spin) GitOps: Initializing Tree Views'; 18 | } 19 | 20 | /** 21 | * Show initialization message in status bar 22 | * (only at the extension initialization (once)) 23 | */ 24 | startLoadingTree(): void { 25 | if (this.loadingWasHidden) { 26 | return; 27 | } 28 | 29 | this.numberOfLoadingTreeViews++; 30 | this.statusBarItem.show(); 31 | } 32 | 33 | /** 34 | * Stop initialization of one tree view. 35 | * (if some tree views are still loading - don't hide yet). 36 | */ 37 | stopLoadingTree(): void { 38 | this.numberOfLoadingTreeViews--; 39 | 40 | if (this.numberOfLoadingTreeViews === 0) { 41 | this.loadingWasHidden = true; 42 | this.statusBarItem.hide(); 43 | this.statusBarItem.dispose(); 44 | } 45 | } 46 | 47 | dispose() { 48 | this.statusBarItem.dispose(); 49 | } 50 | } 51 | 52 | /** 53 | * Status bar for showing extension initialization message. 54 | */ 55 | export const statusBar = new StatusBar(); 56 | -------------------------------------------------------------------------------- /media/createSource.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14px; 3 | background-color: var(--vscode-sideBar-background); 4 | } 5 | 6 | input:not([type='checkbox']):not([type='radio']), 7 | textarea { 8 | padding: 5px; 9 | border: 1px solid var(--vscode-input-border); 10 | } 11 | 12 | h2 { 13 | padding-top: 1em; 14 | } 15 | 16 | hr { 17 | border-style: solid; 18 | border-bottom: none; 19 | border-width: 1px; 20 | border-color: var(--vscode-sideBar-border, var(--vscode-panel-border, '#666')); 21 | } 22 | 23 | code { 24 | font-size: inherit; 25 | } 26 | 27 | input[type="radio"] { 28 | margin-right: 0; 29 | } 30 | input[type="radio"], 31 | input[type="radio"] + label, 32 | input[type="checkbox"], 33 | input[type="checkbox"] + label { 34 | vertical-align: middle; 35 | display: inline-block; 36 | } 37 | 38 | .app { 39 | max-width: max(50%, 800px); 40 | margin-left: auto; 41 | margin-right: auto; 42 | } 43 | 44 | .header-label { 45 | display: block; 46 | margin-bottom: 0.2em; 47 | margin-top: 0.6em; 48 | } 49 | 50 | .submit-button { 51 | width: fit-content; 52 | padding: 6px 12px; 53 | overflow: hidden; 54 | text-overflow: ellipsis; 55 | margin: 10px 0; 56 | float: right; 57 | } 58 | 59 | .cli-argument { 60 | background-color: rgba(255, 255, 255, 0.05); 61 | border: 1px solid rgba(255, 255, 255, 0.15); 62 | border-radius: 0.2em; 63 | padding: 0 0.3em; 64 | } 65 | 66 | .vscode-light .cli-argument { 67 | background-color: rgba(0, 0, 0, 0.02); 68 | border-color: rgba(0, 0, 0, 0.15); 69 | } -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/App.tsx: -------------------------------------------------------------------------------- 1 | import '@vscode/codicons/dist/codicon.css'; 2 | import '@vscode/codicons/dist/codicon.ttf'; 3 | import { allComponents, provideVSCodeDesignSystem } from '@vscode/webview-ui-toolkit'; 4 | import { onMount } from 'solid-js'; 5 | import { vscode } from 'lib/utils/vscode'; 6 | 7 | import { unwrapModel } from 'lib/unwrapModel'; 8 | import { updateParams } from 'lib/params'; 9 | import { debug, debugStandalone } from 'lib/utils/debug'; 10 | 11 | import 'App.css'; 12 | import Main from 'components/Main'; 13 | 14 | provideVSCodeDesignSystem().register(allComponents); 15 | 16 | function receiveParams() { 17 | // Handle the message inside the webview 18 | window.addEventListener('message', event => { 19 | const message = event.data; // The JSON data our extension sent 20 | 21 | switch (message.type) { 22 | case 'set-params': 23 | updateParams(message.params); 24 | break; 25 | } 26 | }); 27 | } 28 | 29 | export function postModel(action: 'create' | 'show-yaml') { 30 | const model = unwrapModel(); 31 | vscode.postMessage({ 32 | action, 33 | data: model, 34 | }); 35 | 36 | console.log(model); 37 | console.log(model.source); 38 | } 39 | 40 | 41 | export default function App() { 42 | onMount(() => { 43 | debug('App mounted'); 44 | vscode.postMessage({ 45 | action: 'init-view', 46 | }); 47 | 48 | receiveParams(); 49 | 50 | if(!vscode.vsCodeApi) { 51 | debugStandalone(); 52 | } 53 | }); 54 | 55 | return ( 56 |
57 | ); 58 | } 59 | 60 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/App.tsx: -------------------------------------------------------------------------------- 1 | import '@vscode/codicons/dist/codicon.css'; 2 | import '@vscode/codicons/dist/codicon.ttf'; 3 | import 'App.css'; 4 | 5 | import { allComponents, provideVSCodeDesignSystem } from '@vscode/webview-ui-toolkit'; 6 | import { onMount } from 'solid-js'; 7 | 8 | import { vscode } from 'lib/utils/vscode'; 9 | import { gitOpsTemplate, setGitOpsTemplate, values } from 'lib/model'; 10 | import { debug, debugStandalone } from 'lib/utils/debug'; 11 | 12 | import Main from 'components/Main'; 13 | 14 | provideVSCodeDesignSystem().register(allComponents); 15 | 16 | function receiveParams() { 17 | // Handle the message inside the webview 18 | window.addEventListener('message', event => { 19 | const message = event.data; // The JSON data our extension sent 20 | 21 | switch (message.type) { 22 | case 'set-params': 23 | setGitOpsTemplate(message.params); 24 | break; 25 | } 26 | }); 27 | } 28 | 29 | export function postModel(action: 'create' | 'show-yaml') { 30 | const model = { 31 | values:Object.fromEntries(values.entries()), 32 | template: gitOpsTemplate(), 33 | }; 34 | vscode.postMessage({ 35 | action, 36 | data: model, 37 | }); 38 | 39 | console.log(model); 40 | } 41 | 42 | 43 | export default function App() { 44 | onMount(() => { 45 | debug('App mounted'); 46 | vscode.postMessage({ 47 | action: 'init-view', 48 | }); 49 | 50 | receiveParams(); 51 | 52 | if(!vscode.vsCodeApi) { 53 | debugStandalone(); 54 | } 55 | }); 56 | 57 | return ( 58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: 'Issue with the GitOps extension' 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Expected behaviour 7 | description: 'What do you think should have happened?' 8 | validations: 9 | required: true 10 | - type: textarea 11 | attributes: 12 | label: Actual behaviour 13 | description: What happened? 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: Steps to reproduce 19 | description: 'Self-contained, minimal reproducing code samples are **extremely** helpful and will expedite addressing your issue. If you think a GIF of what is happening would be helpful, consider tools like https://www.cockos.com/licecap/, https://github.com/phw/peek or https://www.screentogif.com/' 20 | validations: 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: Versions 25 | description: 'Versions of the extension, editor and installed CLI tools. All of the versions can be found via the `GitOps: Show Installed Versions` command in the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) `Ctrl`+`Shift`+`P` (`Cmd`+`Shift`+`P` on Mac).' 26 | value: | 27 | kubectl version: 28 | Flux version: 29 | Git version: 30 | Azure version: 31 | Extension version: 32 | VSCode version: 33 | Operating System (OS) and its version: 34 | validations: 35 | required: true -------------------------------------------------------------------------------- /src/views/nodes/clusterDeploymentNode.ts: -------------------------------------------------------------------------------- 1 | import { Deployment, KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; 2 | import { TreeNode, TreeNodeIcon } from './treeNode'; 3 | 4 | /** 5 | * Defines deployment tree view item for display in GitOps Clusters tree view. 6 | */ 7 | export class ClusterDeploymentNode extends TreeNode { 8 | 9 | /** 10 | * Cluster deployment kubernetes resource object 11 | */ 12 | resource: Deployment; 13 | 14 | constructor(deployment: Deployment) { 15 | super(deployment.metadata.name || ''); 16 | 17 | this.resource = deployment; 18 | 19 | this.label = this.getImageName(deployment); 20 | 21 | this.setIcon(TreeNodeIcon.Unknown); 22 | } 23 | 24 | /** 25 | * Return the name of the image of the container 26 | * @param deployment Flux deployment kubernetes object 27 | * @returns Version of the flux controller or an empty string 28 | */ 29 | getImageName(deployment: Deployment): string { 30 | const fluxControllerContainer = deployment.spec.template.spec?.containers?.find(container => container || ''); 31 | return fluxControllerContainer?.image || ''; 32 | } 33 | 34 | /** 35 | * Show status of deployment by changing the icon. 36 | * @param status Status of this deployment. 37 | */ 38 | setStatus(status: 'success' | 'failure') { 39 | if (status === 'success') { 40 | this.setIcon(TreeNodeIcon.Success); 41 | } else if (status === 'failure') { 42 | this.setIcon(TreeNodeIcon.Warning); 43 | } 44 | } 45 | 46 | get contexts() { 47 | return [KubernetesObjectKinds.Deployment]; 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/commands/fluxReconcileSource.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { fluxTools } from '../flux/fluxTools'; 3 | import { FluxSource } from '../flux/fluxTypes'; 4 | import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; 5 | import { BucketNode } from '../views/nodes/bucketNode'; 6 | import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; 7 | import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; 8 | import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; 9 | import { refreshSourcesTreeView } from '../views/treeViews'; 10 | 11 | /** 12 | * Invoke flux reconcile of a specific source. 13 | * @param source Target source tree view item. 14 | */ 15 | export async function fluxReconcileSourceCommand(source: GitRepositoryNode | OCIRepositoryNode | HelmRepositoryNode | BucketNode): Promise { 16 | 17 | const sourceType: FluxSource | 'unknown' = source.resource.kind === KubernetesObjectKinds.GitRepository ? 'source git' : 18 | source.resource.kind === KubernetesObjectKinds.OCIRepository ? 'source oci' : 19 | source.resource.kind === KubernetesObjectKinds.HelmRepository ? 'source helm' : 20 | source.resource.kind === KubernetesObjectKinds.Bucket ? 'source bucket' : 'unknown'; 21 | 22 | if (sourceType === 'unknown') { 23 | window.showErrorMessage(`Unknown Flux Source resource kind ${source.resource.kind}`); 24 | return; 25 | } 26 | 27 | await fluxTools.reconcile(sourceType, source.resource.metadata.name || '', source.resource.metadata.namespace || ''); 28 | 29 | refreshSourcesTreeView(); 30 | } 31 | -------------------------------------------------------------------------------- /src/commands/showLogs.ts: -------------------------------------------------------------------------------- 1 | import { V1ObjectMeta } from '@kubernetes/client-node'; 2 | import { commands, Uri, window } from 'vscode'; 3 | import { allKinds, ResourceKind } from '../kuberesources'; 4 | import { kubernetesTools } from '../kubernetes/kubernetesTools'; 5 | import { ClusterDeploymentNode } from '../views/nodes/clusterDeploymentNode'; 6 | 7 | interface ResourceNode { 8 | readonly nodeType: 'resource'; 9 | readonly name?: string; 10 | readonly namespace?: string; 11 | readonly kindName: string; 12 | readonly metadata: V1ObjectMeta; 13 | readonly kind: ResourceKind; 14 | uri(outputFormat: string): Uri; 15 | } 16 | 17 | /** 18 | * Show logs in the editor webview (running Kubernetes extension command) 19 | */ 20 | export async function showLogs(deploymentNode: ClusterDeploymentNode): Promise { 21 | 22 | const pods = await kubernetesTools.getPodsOfADeployment(deploymentNode.resource.metadata.name, deploymentNode.resource.metadata.namespace); 23 | const pod = pods?.items[0]; 24 | 25 | if (!pod) { 26 | window.showErrorMessage(`No pods were found from ${deploymentNode.resource.metadata.name} deployment.`); 27 | return; 28 | } 29 | 30 | const podResourceNode: ResourceNode = { 31 | nodeType: 'resource', 32 | name: pod.metadata.name, 33 | namespace: pod.metadata.namespace, 34 | metadata: pod.metadata, 35 | kindName: `pod/${pod.metadata.name}`, 36 | kind: allKinds.pod, 37 | uri(outputFormat: string) { 38 | return kubernetesTools.getResourceUri(this.namespace, this.kindName, outputFormat); 39 | }, 40 | }; 41 | 42 | commands.executeCommand('extension.vsKubernetesLogs', podResourceNode); 43 | } 44 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/unwrapModel.ts: -------------------------------------------------------------------------------- 1 | import { unwrap } from 'solid-js/store'; 2 | import { createWorkload, createSource, kustomization, source, gitRepository, ociRepository, helmRepository, bucket } from './model'; 3 | import { params, ParamsDictionary } from './params'; 4 | 5 | 6 | function unwrapSourceKind() { 7 | switch(source.kind) { 8 | case 'GitRepository': 9 | return unwrap(gitRepository); 10 | 11 | case 'OCIRepository': 12 | return unwrap(ociRepository); 13 | 14 | case 'HelmRepository': 15 | return unwrap(helmRepository); 16 | 17 | case 'Bucket': 18 | return unwrap(bucket); 19 | } 20 | } 21 | 22 | function unwrapSource() { 23 | let s: any = unwrap(source); 24 | 25 | if(s.createSecret && s.kind !== 'OCIRepository') { 26 | s['secretRef'] = ''; 27 | } 28 | 29 | delete s['createSecret']; 30 | 31 | // merge common and specific type source parameters 32 | s = {...s, ...unwrapSourceKind()}; 33 | 34 | 35 | if(s['refType']) { 36 | // s['tag'] = 'latest'; 37 | s[s['refType']] = s['ref']; 38 | delete s['refType']; 39 | delete s['ref']; 40 | } 41 | 42 | 43 | return s; 44 | } 45 | 46 | 47 | function unwrapKustomization() { 48 | const k = unwrap(kustomization); 49 | 50 | if(k.targetNamespace === '') { 51 | k.targetNamespace = ''; 52 | } 53 | 54 | return k; 55 | } 56 | 57 | 58 | export function unwrapModel() { 59 | const model: ParamsDictionary = {}; 60 | 61 | if(createSource()) { 62 | model.source = unwrapSource(); 63 | } 64 | 65 | if(createWorkload()) { 66 | model.kustomization = unwrapKustomization(); 67 | } 68 | 69 | model.clusterInfo = unwrap(params).clusterInfo; 70 | 71 | return model; 72 | } 73 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/utils/debug.ts: -------------------------------------------------------------------------------- 1 | import { setParams } from '../params'; 2 | 3 | export function debug(str: string) { 4 | const e = document.getElementById('debug'); 5 | if(e) { 6 | e.innerHTML = `${e.innerHTML}\n${str}`; 7 | // e.style.display = 'block'; 8 | e.scrollTop = e.scrollHeight; 9 | } 10 | } 11 | 12 | export function debugStandalone() { 13 | const debugParams = { 14 | 'clusterInfo': { 15 | 'contextName': 'kind-context', 16 | 'clusterName': 'kind-cluster', 17 | 'clusterProvider': 'Generic', 18 | 'isClusterProviderUserOverride': false, 19 | 'isAzure': false, 20 | }, 21 | 'gitInfo': { 22 | 'name': 'debug-standalone', 23 | 'url': 'ssh://git@github.com/juozasg/pooodinfo.git', 24 | 'branch': 'master', 25 | }, 26 | 'namespaces': [ 'default', 'flux-system', 'foobar'], 27 | 'sources': [ 28 | {'kind': 'GitRepository', metadata: {'name': 'podinfo', 'namespace': 'default'}}, 29 | {'kind': 'OCIRepository', metadata: {'name': 'podinfo', 'namespace': 'default'}}, 30 | {'kind': 'OCIRepository', metadata: {'name': 'podinfo', 'namespace': 'flux-system'}}, 31 | {'kind': 'GitRepository', metadata: {'name': 'podinfo2', 'namespace': 'default'}}, 32 | {'kind': 'OCIRepository', metadata: {'name': 'podinfo11', 'namespace': 'default'}}], 33 | 'selectSourceTab': false, 34 | 'selectedSource': 'GitRepository/podinfo2.default', 35 | // 'selectedSource': '', 36 | 'set': { 37 | 'kustomization': { 38 | 'path': './test-set-path', 39 | }, 40 | 'createWorkload': true, 41 | }, 42 | }; 43 | 44 | setTimeout(() => { 45 | for(const [param, value] of Object.entries(debugParams)) { 46 | setParams(param, value); 47 | } 48 | }, 300); 49 | } 50 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import { postModel } from 'App'; 2 | import { gitOpsTemplate, TemplateParam, values } from 'lib/model'; 3 | import { For, Show } from 'solid-js'; 4 | import { ParamSelect } from './Common/ParamSelect'; 5 | import ParamInput from './Common/ParamTextInput'; 6 | 7 | 8 | const isSelectOption = (param: TemplateParam) => param.options && param.options.length > 0; 9 | 10 | const requiredParams = () => gitOpsTemplate().params.filter(p => p.required).map(p => p.name); 11 | export const missingParams = () => requiredParams().filter(name => !values.get(name)); 12 | 13 | function ValidationMessage() { 14 | return ( 15 |

Missing required parameters

16 | ); 17 | } 18 | 19 | export default function Main() { 20 | return( 21 |
22 |

Configure {gitOpsTemplate().name}

23 |

{gitOpsTemplate().description}

24 | {(param, i) => 25 | }> 26 | 27 | 28 | } 29 | 30 | 31 | 32 | 33 |
34 | 35 | postModel('show-yaml')} class="big"> 36 | Save 37 | 38 | 39 | 40 |
41 |
42 | Save YAML to: {gitOpsTemplate().folder} 43 |
44 |
45 |
46 |
47 | ); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/commands/trace.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { telemetry } from '../extension'; 3 | import { fluxTools } from '../flux/fluxTools'; 4 | import { kubernetesTools } from '../kubernetes/kubernetesTools'; 5 | import { AnyResourceNode } from '../views/nodes/anyResourceNode'; 6 | import { WorkloadNode } from '../views/nodes/workloadNode'; 7 | 8 | /** 9 | * Run flux trace for the Workloads tree view node. 10 | */ 11 | export async function trace(node: AnyResourceNode | WorkloadNode) { 12 | const resourceName = node.resource.metadata?.name || ''; 13 | const resourceNamespace = node.resource.metadata?.namespace || 'flux-system'; 14 | const resourceKind = node.resource.kind || ''; 15 | let resourceApiVersion = node.resource.apiVersion || ''; 16 | 17 | if (!resourceName) { 18 | window.showErrorMessage('"name" is required to run `flux trace`.'); 19 | telemetry.sendError('"name" is required to run `flux trace`.'); 20 | return; 21 | } 22 | if (!resourceKind) { 23 | window.showErrorMessage('"kind" is required to run `flux trace`'); 24 | telemetry.sendError('"kind" is required to run `flux trace`'); 25 | return; 26 | } 27 | 28 | // flux tree fetched items don't have the "apiVersion" property 29 | if (!resourceApiVersion) { 30 | const resource = await kubernetesTools.getResource(resourceName, resourceNamespace, resourceKind); 31 | const apiVersion = resource?.apiVersion; 32 | if (!apiVersion && !apiVersion) { 33 | window.showErrorMessage('"apiVersion" is required to run `flux trace`'); 34 | telemetry.sendError('"apiVersion" is required to run `flux trace`'); 35 | return; 36 | } 37 | resourceApiVersion = apiVersion; 38 | } 39 | 40 | await fluxTools.trace(resourceName, resourceKind, resourceApiVersion, resourceNamespace); 41 | } 42 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | //@ts-check 3 | 4 | 'use strict'; 5 | 6 | const path = require('path'); 7 | 8 | /**@type {import('webpack').Configuration}*/ 9 | const config = { 10 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 11 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 12 | 13 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 14 | output: { 15 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 16 | path: path.resolve(__dirname, 'dist'), 17 | filename: 'extension.js', 18 | libraryTarget: 'commonjs2', 19 | }, 20 | devtool: 'nosources-source-map', 21 | externals: { 22 | vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 23 | 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // https://github.com/microsoft/vscode-extension-telemetry/issues/41 24 | // modules added here also need to be added in the .vsceignore file 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js'], 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader', 38 | }, 39 | ], 40 | }, 41 | ], 42 | }, 43 | infrastructureLogging: { 44 | level: 'log', // enables logging required for problem matchers 45 | }, 46 | }; 47 | module.exports = config; 48 | -------------------------------------------------------------------------------- /src/terminal.ts: -------------------------------------------------------------------------------- 1 | import { Disposable, ExtensionContext, Terminal, window } from 'vscode'; 2 | import { getExtensionContext } from './extensionContext'; 3 | 4 | const terminalName = 'gitops'; 5 | 6 | let _terminal: Terminal | undefined; 7 | let _currentDirectory: string | undefined; 8 | let _disposable: Disposable | undefined; 9 | 10 | /** 11 | * Gets gitops treminal instance. 12 | * @param context VScode extension context. 13 | * @param workingDirectory Optional working directory path to cd to. 14 | * @returns 15 | */ 16 | function getTerminal(context: ExtensionContext, workingDirectory?: string): Terminal { 17 | if (_terminal === undefined) { 18 | _terminal = window.createTerminal(terminalName); 19 | _disposable = window.onDidCloseTerminal((e: Terminal) => { 20 | if (e.name === terminalName) { 21 | _terminal = undefined; 22 | _disposable?.dispose(); 23 | _disposable = undefined; 24 | } 25 | }); 26 | 27 | context.subscriptions.push(_disposable); 28 | _currentDirectory = undefined; 29 | } 30 | 31 | if (_currentDirectory !== workingDirectory && 32 | workingDirectory && workingDirectory.length > 0) { 33 | _terminal.sendText(`cd "${workingDirectory}"`, true); // add new line 34 | _currentDirectory = workingDirectory; 35 | } 36 | 37 | return _terminal; 38 | } 39 | 40 | /** 41 | * Runs terminal command. 42 | * @param command Command name. 43 | * @param cwd Optional working directory path to cd to. 44 | * @param focusTerminal Optional whether or not to shift the focus to the terminal. 45 | */ 46 | export function runTerminalCommand( 47 | command: string, 48 | { 49 | cwd, 50 | focusTerminal, 51 | }: { 52 | cwd?: string; 53 | focusTerminal?: boolean; 54 | } = {}): void { 55 | 56 | const terminal = getTerminal(getExtensionContext(), cwd); 57 | terminal.show(!focusTerminal); 58 | terminal.sendText(command, true); 59 | } 60 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/toolkit.d.ts: -------------------------------------------------------------------------------- 1 | import "solid-js"; 2 | 3 | // An important part of getting the Webview UI Toolkit to work with 4 | // Solid + TypeScript + JSX is to extend the solid-js JSX.IntrinsicElements 5 | // type interface to include type annotations for each of the toolkit's components. 6 | // 7 | // Without this, type errors will occur when you try to use any toolkit component 8 | // in your Solid + TypeScript + JSX component code. (Note that this file shouldn't be 9 | // necessary if you're not using TypeScript or are using tagged template literals 10 | // instead of JSX for your Solid component code). 11 | // 12 | // Important: This file should be updated whenever a new component is added to the 13 | // toolkit. You can find a list of currently available toolkit components here: 14 | // 15 | // https://github.com/microsoft/vscode-webview-ui-toolkit/blob/main/docs/components.md 16 | // 17 | declare module "solid-js" { 18 | namespace JSX { 19 | interface IntrinsicElements { 20 | "auto-complete": any; 21 | "vscode-badge": any; 22 | "vscode-button": any; 23 | "vscode-checkbox": any; 24 | "vscode-data-grid": any; 25 | "vscode-divider": any; 26 | "vscode-dropdown": any; 27 | "vscode-link": any; 28 | "vscode-option": any; 29 | "vscode-panels": any; 30 | "vscode-panel-tab": any; 31 | "vscode-panel-view": any; 32 | "vscode-progress-ring": any; 33 | "vscode-radio": any; 34 | "vscode-radio-group": any; 35 | "vscode-tag": any; 36 | "vscode-text-area": any; 37 | "vscode-text-field": any; 38 | } 39 | 40 | interface Directives { 41 | bindInputModelSource: any, 42 | bindInputModelKustomization: any, 43 | bindInputSignal: any, 44 | bindInputStore: any, 45 | bindDropdownStore: any, 46 | bindChangeValueFunc: any, 47 | bindChangeValueSignal: any, 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/toolkit.d.ts: -------------------------------------------------------------------------------- 1 | import "solid-js"; 2 | 3 | // An important part of getting the Webview UI Toolkit to work with 4 | // Solid + TypeScript + JSX is to extend the solid-js JSX.IntrinsicElements 5 | // type interface to include type annotations for each of the toolkit's components. 6 | // 7 | // Without this, type errors will occur when you try to use any toolkit component 8 | // in your Solid + TypeScript + JSX component code. (Note that this file shouldn't be 9 | // necessary if you're not using TypeScript or are using tagged template literals 10 | // instead of JSX for your Solid component code). 11 | // 12 | // Important: This file should be updated whenever a new component is added to the 13 | // toolkit. You can find a list of currently available toolkit components here: 14 | // 15 | // https://github.com/microsoft/vscode-webview-ui-toolkit/blob/main/docs/components.md 16 | // 17 | declare module "solid-js" { 18 | namespace JSX { 19 | interface IntrinsicElements { 20 | "auto-complete": any; 21 | "vscode-badge": any; 22 | "vscode-button": any; 23 | "vscode-checkbox": any; 24 | "vscode-data-grid": any; 25 | "vscode-divider": any; 26 | "vscode-dropdown": any; 27 | "vscode-link": any; 28 | "vscode-option": any; 29 | "vscode-panels": any; 30 | "vscode-panel-tab": any; 31 | "vscode-panel-view": any; 32 | "vscode-progress-ring": any; 33 | "vscode-radio": any; 34 | "vscode-radio-group": any; 35 | "vscode-tag": any; 36 | "vscode-text-area": any; 37 | "vscode-text-field": any; 38 | } 39 | 40 | interface Directives { 41 | bindInputModelSource: any, 42 | bindInputModelKustomization: any, 43 | bindInputSignal: any, 44 | bindInputStore: any, 45 | bindDropdownStore: any, 46 | bindChangeValueFunc: any, 47 | bindChangeValueSignal: any, 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from '@microsoft/fast-foundation'; 2 | import { createEffect, onMount, Show } from 'solid-js'; 3 | import { setCreateSource } from 'lib/model'; 4 | import { params } from 'lib/params'; 5 | import { bindChangeTabsFunc } from 'lib/bindDirectives'; bindChangeTabsFunc; // TS will elide 'unused' imports 6 | import NewSource from 'components/Source/NewSource'; 7 | import SelectSource from 'components/Source/SelectSource'; 8 | 9 | let tabs: Tabs; 10 | 11 | function Source() { 12 | const selectSourceEnabled = () => (params.sources?.length > 0); 13 | 14 | onMount(() => { 15 | tabs.addEventListener('change', (e: Event) => setCreateSource(tabs.activeid === 'new-source-tab')); 16 | }); 17 | 18 | createEffect(() => { 19 | const activeid = (params.selectSourceTab && params.sources?.length > 0) ? 'select-source-tab' : 'new-source-tab'; 20 | tabs.activeid = activeid; 21 | setCreateSource(tabs.activeid === 'new-source-tab'); 22 | }); 23 | 24 | 25 | return( 26 |
27 |

Source Repository

28 | {/* */} 29 | 30 | New Source... 31 | 32 | 33 | Select Source 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | ); 47 | } 48 | 49 | export default Source; 50 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/GitRepository/GitConnection.tsx: -------------------------------------------------------------------------------- 1 | import Checkbox from 'components/Common/Checkbox'; 2 | import { ToolkitHelpLink } from 'components/Common/HelpLink'; 3 | import TextInput from 'components/Common/TextInput'; 4 | import { gitRepository, source } from 'lib/model'; 5 | import { Show } from 'solid-js'; 6 | 7 | function SecretRefInput() { 8 | return ( 9 |
10 | 11 | 12 |
13 | ) 14 | ; 15 | } 16 | 17 | const isSSH = () => gitRepository.url.toLowerCase().indexOf('ssh') === 0; 18 | 19 | function SSHPrivateKeyFile() { 20 | return ( 21 |
22 | 23 | 24 |
25 | ); 26 | } 27 | 28 | 29 | function GitConnection() { 30 | return ( 31 |
32 | Create new Secret with credentials 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 |
52 | 53 |
54 | ); 55 | } 56 | 57 | export default GitConnection; 58 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/OCIRepository.tsx: -------------------------------------------------------------------------------- 1 | import ListSelect from 'components/Common/ListSelect'; 2 | import SettingsPanel from './Settings/OCIRepository/Panel'; 3 | import Name from './Common/Name'; 4 | import Namespace from './Common/Namespace'; 5 | import TextInput from 'components/Common/TextInput'; 6 | import { ToolkitHelpLink } from 'components/Common/HelpLink'; 7 | 8 | 9 | function OCIRepository() { 10 | return ( 11 |
12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 | ['tag', 'tagSemver', 'digest']}/> 25 | 26 |
27 |
28 | 29 | 30 |
Authentication settings for private repositories
31 |
32 | 33 |
34 | ['generic', 'aws', 'azure', 'gcp']}/> 37 |
38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 | 51 | 52 |
53 | ); 54 | } 55 | 56 | export default OCIRepository; 57 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource/Settings/HelmRepository/HelmConnection.tsx: -------------------------------------------------------------------------------- 1 | import { Show } from 'solid-js'; 2 | 3 | import TextInput from 'components/Common/TextInput'; 4 | import { isOCIHelm } from '../../HelmRepository'; 5 | import Checkbox from 'components/Common/Checkbox'; 6 | import { source } from 'lib/model'; 7 | import { ToolkitHelpLink } from 'components/Common/HelpLink'; 8 | 9 | 10 | 11 | function SecretRefInput() { 12 | return ( 13 |
14 | 15 | 16 | 17 |
18 | ); 19 | } 20 | 21 | 22 | function HelmConnection() { 23 | return ( 24 |
25 | 26 | Create new Secret with credentials 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 | 43 | 44 |
45 | 46 |
47 | 48 | 49 |
50 | 51 |
52 | 53 | 54 |
55 |
56 |
57 | 58 | 59 | 60 |
61 | ); 62 | } 63 | 64 | export default HelmConnection; 65 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Source/NewSource.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from '@microsoft/fast-foundation'; 2 | import { ToolkitHelpLink } from 'components/Common/HelpLink'; 3 | import { params } from 'lib/params'; 4 | import { onMount, Show } from 'solid-js'; 5 | import { setSource, source } from '../../lib/model'; 6 | 7 | import Bucket from './NewSource/Bucket'; 8 | import GitRepository from './NewSource/GitRepository'; 9 | import HelmRepository from './NewSource/HelmRepository'; 10 | import OCIRepository from './NewSource/OCIRepository'; 11 | 12 | let tabs: Tabs; 13 | 14 | 15 | function NewSource() { 16 | onMount(() => { 17 | tabs.addEventListener('change', (e: Event) => { 18 | const kind = tabs.activeid.slice(0, -4); 19 | setSource('kind', kind); 20 | }); 21 | }); 22 | 23 | 24 | return ( 25 |
26 | 27 | 28 | 29 | GitRepository 30 | 31 | 32 | 33 | 34 | HelmRepository 35 | 36 | 37 | 38 | OCIRepository 39 | 40 | 41 | 42 | Bucket 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | ); 65 | } 66 | 67 | export default NewSource; 68 | -------------------------------------------------------------------------------- /src/commands/createGitRepositoryForPath.ts: -------------------------------------------------------------------------------- 1 | import gitUrlParse from 'git-url-parse'; 2 | import { Uri, window, workspace } from 'vscode'; 3 | import { failed } from '../errorable'; 4 | import { getFolderGitInfo } from '../git/gitInfo'; 5 | import { checkGitVersion } from '../install'; 6 | import { getCurrentClusterInfo } from '../views/treeViews'; 7 | import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; 8 | 9 | /** 10 | * Add git repository source whether from an opened folder 11 | * or make user pick a folder to infer the url & branch 12 | * of the new git repository source. 13 | * @param fileExplorerUri uri of the file in the file explorer 14 | */ 15 | export async function createGitRepositoryForPath(fileExplorerUri?: Uri) { 16 | 17 | const gitInstalled = await checkGitVersion(); 18 | if (!gitInstalled) { 19 | return; 20 | } 21 | 22 | const currentClusterInfo = await getCurrentClusterInfo(); 23 | if (failed(currentClusterInfo)) { 24 | return; 25 | } 26 | 27 | let gitInfo; 28 | 29 | if (fileExplorerUri) { 30 | gitInfo = await getFolderGitInfo(fileExplorerUri.fsPath); 31 | } else { 32 | let gitFolderFsPath = ''; 33 | // executed from Command Palette 34 | if (!workspace.workspaceFolders || workspace.workspaceFolders?.length === 0) { 35 | // no opened folders => show OS dialog 36 | const pickedFolder = await window.showOpenDialog({ 37 | canSelectFiles: false, 38 | canSelectFolders: true, 39 | canSelectMany: false, 40 | }); 41 | if (!pickedFolder) { 42 | return; 43 | } 44 | gitFolderFsPath = pickedFolder[0].fsPath; 45 | } else if (workspace.workspaceFolders.length > 1) { 46 | // multiple folders opened (multi-root) => make user pick one 47 | const pickedFolder = await window.showQuickPick(workspace.workspaceFolders.map(folder => folder.uri.fsPath)); 48 | if (!pickedFolder) { 49 | return; 50 | } 51 | gitFolderFsPath = pickedFolder; 52 | } else { 53 | // just one folder opened => use it 54 | gitFolderFsPath = workspace.workspaceFolders[0].uri.fsPath; 55 | } 56 | 57 | gitInfo = await getFolderGitInfo(gitFolderFsPath); 58 | } 59 | 60 | openConfigureGitOpsWebview(false, '', {createWorkload: false}, gitInfo); 61 | } 62 | -------------------------------------------------------------------------------- /src/commands/showInstalledVersions.ts: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | import { env, extensions, version, window } from 'vscode'; 3 | import { failed } from '../errorable'; 4 | import { GitOpsExtensionConstants } from '../extension'; 5 | import { getAzureVersion, getFluxVersion, getGitVersion, getKubectlVersion } from '../install'; 6 | 7 | /** 8 | * Show all installed cli versions. 9 | */ 10 | export async function showInstalledVersions() { 11 | const [kubectlVersion, fluxVersion, gitVersion, azureVersion] = await Promise.all([ 12 | getKubectlVersion(), 13 | getFluxVersion(), 14 | getGitVersion(), 15 | getAzureVersion(), 16 | ]); 17 | 18 | const kubectlVersionString = failed(kubectlVersion) ? 'kubectl: not found' : `kubectl client ${kubectlVersion.result.clientVersion.gitVersion}\nkubectl server ${kubectlVersion.result.serverVersion.gitVersion}`; 19 | 20 | let azureVersionsString = ''; 21 | if (failed(azureVersion)) { 22 | azureVersionsString = 'Azure: not found'; 23 | } else { 24 | azureVersionsString = ` 25 | Azure: ${azureVersion.result['azure-cli']} 26 | Azure extension "k8s-configuration": ${azureVersion.result.extensions['k8s-configuration'] || 'not installed'} 27 | Azure extension "k8s-extension": ${azureVersion.result.extensions['k8s-extension'] || 'not installed'} 28 | `.trim(); 29 | } 30 | 31 | const versions = ` 32 | ${kubectlVersionString} 33 | Flux: ${failed(fluxVersion) ? 'not found' : fluxVersion.result} 34 | Git: ${failed(gitVersion) ? 'not found' : gitVersion.result} 35 | ${azureVersionsString} 36 | VSCode: ${version} 37 | Extension: ${getExtensionVersion() || 'unknown'} 38 | OS: ${getOSVersion() || 'unknown'} 39 | `.trim(); 40 | 41 | const copyButton = 'Copy'; 42 | const pressedButton = await window.showInformationMessage(versions, { modal: true }, copyButton); 43 | if (pressedButton === copyButton) { 44 | env.clipboard.writeText(versions); 45 | } 46 | } 47 | 48 | /** 49 | * Get installed version of GitOps extension (from `package.json`). 50 | */ 51 | export function getExtensionVersion() { 52 | return extensions.getExtension(GitOpsExtensionConstants.ExtensionId)?.packageJSON.version || 'unknown version'; 53 | } 54 | /** 55 | * Get Operating System its verison. 56 | */ 57 | function getOSVersion() { 58 | return `${os.type} ${os.arch} ${os.release}`; 59 | } 60 | -------------------------------------------------------------------------------- /resources/icons/dark/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 35 | 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /resources/icons/light/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 35 | 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /src/globalState.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, window, workspace } from 'vscode'; 2 | import { KnownClusterProviders } from './kubernetes/types/kubernetesTypes'; 3 | 4 | export interface ClusterMetadata { 5 | azureResourceGroup?: string; 6 | azureSubscription?: string; 7 | azureClusterName?: string; 8 | clusterProvider?: KnownClusterProviders; 9 | } 10 | 11 | const enum GlobalStatePrefixes { 12 | ClusterMetadata = 'clusterMetadata', 13 | } 14 | 15 | export const enum GlobalStateKey { 16 | FluxPath = 'fluxPath', 17 | FirstEverActivationStorageKey = 'firstEverActivation', 18 | } 19 | 20 | interface GlobalStateKeyMapping { 21 | [GlobalStateKey.FluxPath]: string; 22 | [GlobalStateKey.FirstEverActivationStorageKey]: boolean; 23 | } 24 | 25 | export class GlobalState { 26 | 27 | constructor(private context: ExtensionContext) {} 28 | 29 | private prefix(prefixValue: GlobalStatePrefixes, str: string): string { 30 | return `${prefixValue}:${str}`; 31 | } 32 | 33 | get(stateKey: E): T[E] | undefined { 34 | return this.context.globalState.get(stateKey as string); 35 | } 36 | 37 | set(stateKey: E, newValue: T[E]): void { 38 | this.context.globalState.update(stateKey as string, newValue); 39 | } 40 | 41 | getClusterMetadata(clusterName: string): ClusterMetadata | undefined { 42 | return this.context.globalState.get(this.prefix(GlobalStatePrefixes.ClusterMetadata, clusterName)); 43 | } 44 | 45 | setClusterMetadata(clusterName: string, metadata: ClusterMetadata): void { 46 | this.context.globalState.update(this.prefix(GlobalStatePrefixes.ClusterMetadata, clusterName), metadata); 47 | } 48 | 49 | /** 50 | * Run while developing to see the entire global storage contents. 51 | */ 52 | async showGlobalStateValue() { 53 | const document = await workspace.openTextDocument({ 54 | language: 'jsonc', 55 | // @ts-ignore 56 | content: JSON.stringify(this.context.globalState._value, null, ' '), 57 | }); 58 | window.showTextDocument(document); 59 | } 60 | 61 | /** 62 | * Dev function (clear all global state properties). 63 | */ 64 | clearGlobalState() { 65 | for (const key of this.context.globalState.keys()) { 66 | this.context.globalState.update(key, undefined); 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/resources/icons/dark/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 35 | 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/resources/icons/dark/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 35 | 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/resources/icons/light/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 35 | 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/resources/icons/light/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 35 | 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /src/utils/stringUtils.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Shortens revision string for display in GitOps tree views. 4 | * @param revision Revision string to shorten. 5 | * @returns Short revision string with max 8 characters for hash, all characters for semantic versions like 0.1.1-staging 6 | */ 7 | export function shortenRevision(revision = ''): string { 8 | revision = revision.replace(/^(sha1|sha256|sha384|sha512|blake3):/, ''); 9 | revision = revision.replace(/@(sha1|sha256|sha384|sha512|blake3):/, '/'); 10 | if (revision.includes('/')) { 11 | // git revision includes branch name 12 | const [gitBranch, gitRevision] = revision.split('/'); 13 | return [gitBranch, ':', gitRevision.slice(0, 7)].join(''); 14 | } else { 15 | return shortenRevisionHash(revision); 16 | } 17 | } 18 | 19 | function shortenRevisionHash(revision: string) { 20 | // return full semver like 0.1.1-staging 21 | if(revision.match(/^\d+\.\d+\.\d+/)) { 22 | return revision; 23 | } 24 | 25 | // trim hash to 8 26 | return revision.slice(0, 7); 27 | } 28 | 29 | /** 30 | * Remove not allowed symbols, cast letters to lowercase 31 | * and truncate the string to match the RFC 1123 subdomain: 32 | * 33 | * - contain no more than 253 characters 34 | * - contain only lowercase alphanumeric characters, '-' or '.' 35 | * - start with an alphanumeric character 36 | * - end with an alphanumeric character 37 | * @param str string to sanitize 38 | */ 39 | export function sanitizeRFC1123(str: string): string { 40 | const notAllowedSymbolsRegex = /[^a-z0-9.-]/g; 41 | const notAllowedSymbolsAtTheStartRegex = /^[^a-z0-9]+/; 42 | const notAllowedSymbolsAtTheEndRegex = /[^a-z0-9]+$/; 43 | 44 | const lowercaseString = str.toLocaleLowerCase(); 45 | 46 | const sanitizedString = lowercaseString 47 | .replace(notAllowedSymbolsRegex, '') 48 | .replace(notAllowedSymbolsAtTheStartRegex, '') 49 | .replace(notAllowedSymbolsAtTheEndRegex, ''); 50 | 51 | return truncateString(sanitizedString, 253); 52 | } 53 | 54 | /** 55 | * Reduce the string length if it's longer than the allowed number of characters. 56 | * @param str string to truncate 57 | * @param maxChars maximum length of the string 58 | */ 59 | export function truncateString(str: string, maxChars: number): string { 60 | const chars = [...str]; 61 | return chars.length > maxChars ? chars.slice(0, maxChars).join('') : str; 62 | } 63 | -------------------------------------------------------------------------------- /src/commands/createKustomizationForPath.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Uri, window, workspace } from 'vscode'; 3 | import { failed } from '../errorable'; 4 | import { getFolderGitInfo, getGitRepositoryforGitInfo } from '../git/gitInfo'; 5 | import { namespacedObject } from '../kubernetes/types/flux/object'; 6 | import { getCurrentClusterInfo } from '../views/treeViews'; 7 | import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; 8 | 9 | /** 10 | * Create kustomization from File Explorer context menu 11 | * or `+` button or Command Palette. 12 | * 13 | * @param fileExplorerUri uri of the file in the file explorer 14 | */ 15 | export async function createKustomizationForPath(fileExplorerUri?: Uri): Promise { 16 | 17 | const currentClusterInfo = await getCurrentClusterInfo(); 18 | if (failed(currentClusterInfo)) { 19 | return; 20 | } 21 | 22 | let kustomizationFsPath = ''; 23 | let relativeKustomizationPath = ''; 24 | 25 | if (fileExplorerUri) { 26 | // executed from the VSCode File Explorer 27 | kustomizationFsPath = fileExplorerUri.fsPath; 28 | } else { 29 | // executed from Command Palette 30 | const pickedFolder = await window.showOpenDialog({ 31 | title: 'Select a folder (used for "path" property on the new Kustomization object)', 32 | canSelectFiles: false, 33 | canSelectFolders: true, 34 | canSelectMany: false, 35 | defaultUri: workspace.workspaceFolders?.[0].uri, 36 | }); 37 | if (!pickedFolder) { 38 | return; 39 | } 40 | kustomizationFsPath = pickedFolder[0].fsPath; 41 | } 42 | 43 | // get relative path for the kustomization 44 | for (const folder of workspace.workspaceFolders || []) { 45 | const relativePath = path.relative(folder.uri.fsPath, kustomizationFsPath); 46 | if (relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath)) { 47 | relativeKustomizationPath = relativePath; 48 | break; 49 | } 50 | } 51 | 52 | const gitInfo = await getFolderGitInfo(kustomizationFsPath); 53 | const gr = await getGitRepositoryforGitInfo(gitInfo); 54 | 55 | const selectSource = !!gr; 56 | let sourceName = namespacedObject(gr) || ''; 57 | 58 | openConfigureGitOpsWebview(selectSource, sourceName, { 59 | kustomization: { 60 | path: relativeKustomizationPath, 61 | }, 62 | createWorkload: true, 63 | }, gitInfo); 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /media/vscode.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --container-padding: 20px; 3 | --input-padding-vertical: 6px; 4 | --input-padding-horizontal: 4px; 5 | --input-margin-vertical: 4px; 6 | --input-margin-horizontal: 0; 7 | } 8 | 9 | body { 10 | padding: 0 var(--container-padding); 11 | color: var(--vscode-foreground); 12 | font-size: var(--vscode-font-size); 13 | font-weight: var(--vscode-font-weight); 14 | font-family: var(--vscode-font-family); 15 | background-color: var(--vscode-editor-background); 16 | } 17 | 18 | ol, 19 | ul { 20 | padding-left: var(--container-padding); 21 | } 22 | 23 | body > *, 24 | form > * { 25 | margin-block-start: var(--input-margin-vertical); 26 | margin-block-end: var(--input-margin-vertical); 27 | } 28 | 29 | *:focus { 30 | outline-color: var(--vscode-focusBorder) !important; 31 | } 32 | 33 | a { 34 | color: var(--vscode-textLink-foreground); 35 | } 36 | 37 | a:hover, 38 | a:active { 39 | color: var(--vscode-textLink-activeForeground); 40 | } 41 | 42 | code { 43 | font-size: var(--vscode-editor-font-size); 44 | font-family: var(--vscode-editor-font-family); 45 | } 46 | 47 | button { 48 | border: none; 49 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 50 | width: 100%; 51 | text-align: center; 52 | outline: 1px solid transparent; 53 | outline-offset: 2px !important; 54 | color: var(--vscode-button-foreground); 55 | background: var(--vscode-button-background); 56 | } 57 | 58 | button:hover { 59 | cursor: pointer; 60 | background: var(--vscode-button-hoverBackground); 61 | } 62 | 63 | button:focus { 64 | outline-color: var(--vscode-focusBorder); 65 | } 66 | 67 | button.secondary { 68 | color: var(--vscode-button-secondaryForeground); 69 | background: var(--vscode-button-secondaryBackground); 70 | } 71 | 72 | button.secondary:hover { 73 | background: var(--vscode-button-secondaryHoverBackground); 74 | } 75 | 76 | input:not([type='checkbox']):not([type='radio']), 77 | textarea { 78 | display: block; 79 | width: 100%; 80 | border: none; 81 | font-family: var(--vscode-font-family); 82 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 83 | color: var(--vscode-input-foreground); 84 | outline-color: var(--vscode-input-border); 85 | background-color: var(--vscode-input-background); 86 | } 87 | 88 | input::placeholder, 89 | textarea::placeholder { 90 | color: var(--vscode-input-placeholderForeground); 91 | } -------------------------------------------------------------------------------- /src/commands/resume.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; 3 | import { failed } from '../errorable'; 4 | import { fluxTools } from '../flux/fluxTools'; 5 | import { FluxSource, FluxWorkload } from '../flux/fluxTypes'; 6 | import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; 7 | import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; 8 | import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; 9 | import { KustomizationNode } from '../views/nodes/kustomizationNode'; 10 | import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; 11 | import { getCurrentClusterInfo, refreshSourcesTreeView, refreshWorkloadsTreeView } from '../views/treeViews'; 12 | 13 | /** 14 | * Resume source or workload reconciliation and refresh its Tree View. 15 | * 16 | * @param node sources tree view node 17 | */ 18 | export async function resume(node: GitRepositoryNode | HelmReleaseNode | HelmRepositoryNode | KustomizationNode) { 19 | 20 | const currentClusterInfo = await getCurrentClusterInfo(); 21 | if (failed(currentClusterInfo)) { 22 | return; 23 | } 24 | 25 | const fluxResourceType: FluxSource | FluxWorkload | 'unknown' = node instanceof GitRepositoryNode ? 26 | 'source git' : node instanceof HelmRepositoryNode ? 27 | 'source helm' : node instanceof OCIRepositoryNode ? 28 | 'source oci' : node instanceof HelmReleaseNode ? 29 | 'helmrelease' : node instanceof KustomizationNode ? 30 | 'kustomization' : 'unknown'; 31 | if (fluxResourceType === 'unknown') { 32 | window.showErrorMessage(`Unknown object kind ${fluxResourceType}`); 33 | return; 34 | } 35 | 36 | if (currentClusterInfo.result.isAzure) { 37 | // TODO: implement 38 | if (fluxResourceType === 'helmrelease' || fluxResourceType === 'kustomization') { 39 | window.showInformationMessage('Not implemented on AKS/ARC', { modal: true }); 40 | return; 41 | } 42 | await azureTools.resume(node.resource.metadata.name || '', currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); 43 | } else { 44 | await fluxTools.resume(fluxResourceType, node.resource.metadata.name || '', node.resource.metadata.namespace || ''); 45 | } 46 | 47 | if (node instanceof GitRepositoryNode || node instanceof OCIRepositoryNode || node instanceof HelmRepositoryNode) { 48 | refreshSourcesTreeView(); 49 | if (currentClusterInfo.result.isAzure) { 50 | refreshWorkloadsTreeView(); 51 | } 52 | } else { 53 | refreshWorkloadsTreeView(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/components/Kustomization.tsx: -------------------------------------------------------------------------------- 1 | import { Show } from 'solid-js'; 2 | 3 | import Checkbox from 'components/Common/Checkbox'; 4 | import ListSelect from 'components/Common/ListSelect'; 5 | import { ToolkitHelpLink } from './Common/HelpLink'; 6 | import TextInput from './Common/TextInput'; 7 | 8 | import { createSource, createWorkload, kustomization, setCreateWorkload, source } from 'lib/model'; 9 | import { params } from 'lib/params'; 10 | 11 | function Kustomization() { 12 | const isAzure = () => params.clusterInfo?.isAzure && (!createSource() || (source.kind === 'GitRepository' && source.createFluxConfig)); 13 | 14 | const targetNamespaces = () => [...(params.namespaces?.values() || []), '']; 15 | 16 | return( 17 |
18 |

Create Kustomization

19 |
20 | 21 | Create a Kustomization 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 |
33 | params.namespaces} 36 | class='medium'/> 37 |
38 | 39 |
40 |
41 |
42 | 43 | 44 |
45 | 46 |
47 | 48 |
49 | 53 |
Namespace for objects reconciled by the Kustomization
54 |
55 |
56 |
57 |
58 | 59 | 60 |
61 | 62 |
63 | 64 | Prune (remove stale resources) 65 | 66 |
67 | 68 |
69 |
70 | ); 71 | } 72 | 73 | export default Kustomization; 74 | -------------------------------------------------------------------------------- /src/commands/showNewUserGuide.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import * as vscode from 'vscode'; 3 | import { tim } from 'tinytim'; 4 | import { asAbsolutePath } from '../extensionContext'; 5 | 6 | 7 | export function showNewUserGuide() { 8 | const panel = vscode.window.createWebviewPanel( 9 | 'gitopsNewUserGuide', // Identifies the type of the webview. Used internally 10 | 'Welcome to GitOps - New User Guide', 11 | vscode.ViewColumn.One, // Editor column to show the new webview panel in. 12 | { 13 | enableScripts: false, 14 | }, 15 | 16 | ); 17 | 18 | panel.iconPath = asAbsolutePath('resources/icons/gitops-logo.png'); 19 | panel.webview.html = getWebviewContent(panel.webview); 20 | } 21 | 22 | function getWebviewContent(webview: vscode.Webview) { 23 | 24 | const styleResetPath = webview.asWebviewUri(asAbsolutePath('media/reset.css')); 25 | const styleVSCodePath = webview.asWebviewUri(asAbsolutePath('media/vscode.css')); 26 | const styleNewUserGuide = webview.asWebviewUri(asAbsolutePath('media/newUserGuide.css')); 27 | 28 | const images = [ 29 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/01-enable-gitops.gif')), 30 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/02-create-source.gif')), 31 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/03-describe-source.gif')), 32 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/04-create-kustomization.gif')), 33 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/05-workloads.gif')), 34 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/06-reconcile.gif')), 35 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/07-logs.png')), 36 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/08-trace.png')), 37 | webview.asWebviewUri(asAbsolutePath('resources/images/newUserGuide/09-docs.png')), 38 | ]; 39 | 40 | 41 | const htmlTemplate = readFileSync(asAbsolutePath('media/newUserGuide.html').fsPath).toString(); 42 | const htmlBody = tim(htmlTemplate, {images: images}); 43 | 44 | return ` 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Welcome to GitOps - New User Guide 53 | 54 | 55 | ${htmlBody} 56 | 57 | 58 | `; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/commands/suspend.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; 3 | import { failed } from '../errorable'; 4 | import { fluxTools } from '../flux/fluxTools'; 5 | import { FluxSource, FluxWorkload } from '../flux/fluxTypes'; 6 | import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; 7 | import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; 8 | import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; 9 | import { KustomizationNode } from '../views/nodes/kustomizationNode'; 10 | import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; 11 | import { getCurrentClusterInfo, refreshSourcesTreeView, refreshWorkloadsTreeView } from '../views/treeViews'; 12 | 13 | /** 14 | * Suspend source or workload reconciliation and refresh its Tree View. 15 | * 16 | * @param node sources tree view node 17 | */ 18 | export async function suspend(node: GitRepositoryNode | HelmReleaseNode | KustomizationNode | HelmRepositoryNode) { 19 | 20 | const currentClusterInfo = await getCurrentClusterInfo(); 21 | if (failed(currentClusterInfo)) { 22 | return; 23 | } 24 | 25 | const fluxResourceType: FluxSource | FluxWorkload | 'unknown' = node instanceof GitRepositoryNode ? 26 | 'source git' : node instanceof HelmRepositoryNode ? 27 | 'source helm' : node instanceof OCIRepositoryNode ? 28 | 'source oci' : node instanceof HelmReleaseNode ? 29 | 'helmrelease' : node instanceof KustomizationNode ? 30 | 'kustomization' : 'unknown'; 31 | 32 | if (fluxResourceType === 'unknown') { 33 | window.showErrorMessage(`Unknown object kind ${fluxResourceType}`); 34 | return; 35 | } 36 | 37 | if (currentClusterInfo.result.isAzure) { 38 | // TODO: implement 39 | if (fluxResourceType === 'helmrelease' || fluxResourceType === 'kustomization') { 40 | window.showInformationMessage('Not implemented on AKS/ARC', { modal: true }); 41 | return; 42 | } 43 | 44 | await azureTools.suspend(node.resource.metadata.name || '', currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); 45 | } else { 46 | await fluxTools.suspend(fluxResourceType, node.resource.metadata.name || '', node.resource.metadata.namespace || ''); 47 | } 48 | 49 | if (node instanceof GitRepositoryNode || node instanceof OCIRepositoryNode || node instanceof HelmRepositoryNode) { 50 | refreshSourcesTreeView(); 51 | if (currentClusterInfo.result.isAzure) { 52 | refreshWorkloadsTreeView(); 53 | } 54 | } else { 55 | refreshWorkloadsTreeView(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/kuberesources.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickItem } from 'vscode'; 2 | 3 | /** 4 | * A class from Kubernetes resources. 5 | * Needed to pass arguments to that extension. 6 | */ 7 | export class ResourceKind implements QuickPickItem { 8 | constructor(readonly displayName: string, readonly pluralDisplayName: string, readonly manifestKind: string, readonly abbreviation: string, readonly apiName?: string) { 9 | } 10 | 11 | get label() { 12 | return this.displayName; 13 | } 14 | get description() { 15 | return ''; 16 | } 17 | } 18 | 19 | /** 20 | * Kubernetes resource kinds implementing {@link QuickPickItem} 21 | */ 22 | export const allKinds = { 23 | // endpoint: new ResourceKind('Endpoint', 'Endpoints', 'Endpoint', 'endpoints', 'endpoints'), 24 | // namespace: new ResourceKind('Namespace', 'Namespaces', 'Namespace', 'namespace' , 'namespaces'), 25 | // node: new ResourceKind('Node', 'Nodes', 'Node', 'node', 'nodes'), 26 | deployment: new ResourceKind('Deployment', 'Deployments', 'Deployment', 'deployment', 'deployments'), 27 | // daemonSet: new ResourceKind('DaemonSet', 'DaemonSets', 'DaemonSet', 'daemonset', 'daemonsets'), 28 | // replicaSet: new ResourceKind('ReplicaSet', 'ReplicaSets', 'ReplicaSet', 'rs', 'replicasets'), 29 | // replicationController: new ResourceKind('Replication Controller', 'Replication Controllers', 'ReplicationController', 'rc', 'replicationcontrollers'), 30 | // job: new ResourceKind('Job', 'Jobs', 'Job', 'job', 'jobs'), 31 | // cronjob: new ResourceKind('CronJob', 'CronJobs', 'CronJob', 'cronjob', 'cronjobs'), 32 | pod: new ResourceKind('Pod', 'Pods', 'Pod', 'pod', 'pods'), 33 | // crd: new ResourceKind('Custom Resource', 'Custom Resources', 'CustomResourceDefinition', 'crd', 'customresources'), 34 | // service: new ResourceKind('Service', 'Services', 'Service', 'service', 'services'), 35 | // configMap: new ResourceKind('ConfigMap', 'Config Maps', 'ConfigMap', 'configmap', 'configmaps'), 36 | // secret: new ResourceKind('Secret', 'Secrets', 'Secret', 'secret', 'secrets'), 37 | // ingress: new ResourceKind('Ingress', 'Ingress', 'Ingress', 'ingress', 'ingress'), 38 | // persistentVolume: new ResourceKind('Persistent Volume', 'Persistent Volumes', 'PersistentVolume', 'pv', 'persistentvolumes'), 39 | // persistentVolumeClaim: new ResourceKind('Persistent Volume Claim', 'Persistent Volume Claims', 'PersistentVolumeClaim', 'pvc', 'persistentvolumeclaims'), 40 | // storageClass: new ResourceKind('Storage Class', 'Storage Classes', 'StorageClass', 'sc', 'storageclasses'), 41 | // statefulSet: new ResourceKind('StatefulSet', 'StatefulSets', 'StatefulSet', 'statefulset', 'statefulsets'), 42 | }; 43 | -------------------------------------------------------------------------------- /src/output.ts: -------------------------------------------------------------------------------- 1 | import { OutputChannel, window } from 'vscode'; 2 | 3 | type OutputChannelName = 'GitOps' | 'GitOps: kubectl'; 4 | 5 | class Output { 6 | /** Main GitOps output channel. */ 7 | private channel: OutputChannel; 8 | /** Channel for kubectl commands output (json) */ 9 | private kubectlChannel: OutputChannel; 10 | 11 | constructor() { 12 | this.channel = window.createOutputChannel('GitOps' as OutputChannelName); 13 | this.kubectlChannel = window.createOutputChannel('GitOps: kubectl' as OutputChannelName); 14 | } 15 | 16 | /** 17 | * Send a message to one of the Output Channels of this extension. 18 | */ 19 | send( 20 | message: string, 21 | { 22 | newline = 'double', 23 | revealOutputView = true, 24 | logLevel = 'info', 25 | channelName = 'GitOps', 26 | }: { 27 | newline?: 'none' | 'single' | 'double'; 28 | revealOutputView?: boolean; 29 | logLevel?: 'info' | 'warn' | 'error'; 30 | channelName?: OutputChannelName; 31 | } = {}, 32 | ): void { 33 | 34 | let channel = this.getChannelByName(channelName); 35 | 36 | if (!channel) { 37 | channel = window.createOutputChannel(channelName); 38 | if (channelName === 'GitOps') { 39 | this.channel = channel; 40 | } else if (channelName === 'GitOps: kubectl') { 41 | this.kubectlChannel = channel; 42 | } 43 | } 44 | 45 | if (revealOutputView) { 46 | channel.show(true); 47 | } 48 | 49 | if (logLevel === 'warn') { 50 | message = `WARN ${message}`; 51 | } else if (logLevel === 'error') { 52 | message = `ERROR ${message}`; 53 | } 54 | 55 | // enforce newlines at the end, but don't append to the existing ones 56 | if (newline === 'single') { 57 | message = `${message.replace(/\n$/, '')}\n`; 58 | } else if (newline === 'double') { 59 | message = `${message.replace(/\n?\n$/, '')}\n\n`; 60 | } 61 | 62 | channel.append(message); 63 | } 64 | 65 | /** 66 | * Show and focus main output channel. 67 | */ 68 | show(): void { 69 | this.channel.show(); 70 | } 71 | 72 | /** 73 | * Return Output channel from its name. 74 | * 75 | * @param channelName Target Output Channel name 76 | */ 77 | private getChannelByName(channelName: OutputChannelName): OutputChannel | undefined { 78 | if (channelName === 'GitOps') { 79 | return this.channel; 80 | } else if (channelName === 'GitOps: kubectl') { 81 | return this.kubectlChannel; 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * Output view of this extension. 88 | */ 89 | export const output = new Output(); 90 | 91 | /** 92 | * @see {@link output.show} 93 | */ 94 | export function showOutputChannel() { 95 | output.show(); 96 | } 97 | -------------------------------------------------------------------------------- /src/commands/deleteSource.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; 3 | import { failed } from '../errorable'; 4 | import { telemetry } from '../extension'; 5 | import { fluxTools } from '../flux/fluxTools'; 6 | import { FluxSource } from '../flux/fluxTypes'; 7 | import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; 8 | import { TelemetryEventNames } from '../telemetry'; 9 | import { BucketNode } from '../views/nodes/bucketNode'; 10 | import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; 11 | import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; 12 | import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; 13 | import { getCurrentClusterInfo, refreshSourcesTreeView, refreshWorkloadsTreeView } from '../views/treeViews'; 14 | 15 | /** 16 | * Delete a source 17 | * 18 | * @param sourceNode Sources tree view node 19 | */ 20 | export async function deleteSource(sourceNode: GitRepositoryNode | OCIRepositoryNode | HelmRepositoryNode | BucketNode) { 21 | 22 | const sourceName = sourceNode.resource.metadata.name || ''; 23 | const sourceNamespace = sourceNode.resource.metadata.namespace || ''; 24 | const confirmButton = 'Delete'; 25 | 26 | const sourceType: FluxSource | 'unknown' = sourceNode.resource.kind === KubernetesObjectKinds.GitRepository ? 'source git' : 27 | sourceNode.resource.kind === KubernetesObjectKinds.HelmRepository ? 'source helm' : 28 | sourceNode.resource.kind === KubernetesObjectKinds.OCIRepository ? 'source oci' : 29 | sourceNode.resource.kind === KubernetesObjectKinds.Bucket ? 'source bucket' : 'unknown'; 30 | 31 | if (sourceType === 'unknown') { 32 | window.showErrorMessage(`Unknown Source resource kind ${sourceNode.resource.kind}`); 33 | return; 34 | } 35 | 36 | const pressedButton = await window.showWarningMessage(`Do you want to delete ${sourceNode.resource.kind} "${sourceName}"?`, { 37 | modal: true, 38 | }, confirmButton); 39 | if (!pressedButton) { 40 | return; 41 | } 42 | 43 | telemetry.send(TelemetryEventNames.DeleteSource, { 44 | kind: sourceNode.resource.kind, 45 | }); 46 | 47 | const currentClusterInfo = await getCurrentClusterInfo(); 48 | if (failed(currentClusterInfo)) { 49 | return; 50 | } 51 | 52 | if (currentClusterInfo.result.isAzure) { 53 | await azureTools.deleteSource(sourceName, currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); 54 | refreshWorkloadsTreeView(); 55 | } else { 56 | await fluxTools.delete(sourceType, sourceName, sourceNamespace); 57 | } 58 | 59 | refreshSourcesTreeView(); 60 | } 61 | -------------------------------------------------------------------------------- /src/commands/deleteWorkload.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; 3 | import { failed } from '../errorable'; 4 | import { telemetry } from '../extension'; 5 | import { fluxTools } from '../flux/fluxTools'; 6 | import { FluxWorkload } from '../flux/fluxTypes'; 7 | import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; 8 | import { TelemetryEventNames } from '../telemetry'; 9 | import { KustomizationNode } from '../views/nodes/kustomizationNode'; 10 | import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; 11 | import { getCurrentClusterInfo, refreshWorkloadsTreeView } from '../views/treeViews'; 12 | 13 | 14 | /** 15 | * Delete a workload 16 | * 17 | * @param workloadNode Workloads tree view node 18 | */ 19 | export async function deleteWorkload(workloadNode: KustomizationNode | HelmReleaseNode) { 20 | 21 | const workloadName = workloadNode.resource.metadata.name || ''; 22 | const workloadNamespace = workloadNode.resource.metadata.namespace || ''; 23 | const confirmButton = 'Delete'; 24 | 25 | let workloadType: FluxWorkload; 26 | switch(workloadNode.resource.kind) { 27 | case KubernetesObjectKinds.Kustomization: { 28 | workloadType = 'kustomization'; 29 | break; 30 | } 31 | 32 | case KubernetesObjectKinds.HelmRelease: { 33 | workloadType = 'helmrelease'; 34 | break; 35 | } 36 | 37 | default: { 38 | return; 39 | } 40 | } 41 | 42 | const currentClusterInfo = await getCurrentClusterInfo(); 43 | if (failed(currentClusterInfo)) { 44 | return; 45 | } 46 | 47 | if (currentClusterInfo.result.isAzure && workloadType !== 'kustomization') { 48 | window.showWarningMessage('Delete HelmRelease not supported on Azure cluster.'); 49 | return; 50 | } 51 | 52 | 53 | 54 | const pressedButton = await window.showWarningMessage(`Do you want to delete ${workloadNode.resource.kind} "${workloadName}"?`, { 55 | modal: true, 56 | }, confirmButton); 57 | if (!pressedButton) { 58 | return; 59 | } 60 | 61 | telemetry.send(TelemetryEventNames.DeleteWorkload, { 62 | kind: workloadNode.resource.kind, 63 | }); 64 | 65 | 66 | if (currentClusterInfo.result.isAzure && workloadType === 'kustomization') { 67 | const fluxConfigName = (workloadNode.resource.spec as any).sourceRef?.name; 68 | const azResourceName = azureTools.getAzName(fluxConfigName, workloadName); 69 | await azureTools.deleteKustomization(fluxConfigName, azResourceName, currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); 70 | } else { 71 | await fluxTools.delete(workloadType, workloadName, workloadNamespace); 72 | } 73 | 74 | refreshWorkloadsTreeView(); 75 | } 76 | -------------------------------------------------------------------------------- /src/views/dataProviders/sourceDataProvider.ts: -------------------------------------------------------------------------------- 1 | import { ContextTypes, setVSCodeContext } from '../../vscodeContext'; 2 | import { kubernetesTools } from '../../kubernetes/kubernetesTools'; 3 | import { statusBar } from '../../statusBar'; 4 | import { BucketNode } from '../nodes/bucketNode'; 5 | import { GitRepositoryNode } from '../nodes/gitRepositoryNode'; 6 | import { OCIRepositoryNode } from '../nodes/ociRepositoryNode'; 7 | import { HelmRepositoryNode } from '../nodes/helmRepositoryNode'; 8 | import { SourceNode } from '../nodes/sourceNode'; 9 | import { DataProvider } from './dataProvider'; 10 | import { sortByMetadataName } from '../../kubernetes/kubernetesUtils'; 11 | import { NamespaceNode } from '../nodes/namespaceNode'; 12 | 13 | /** 14 | * Defines Sources data provider for loading Git/Helm repositories 15 | * and Buckets in GitOps Sources tree view. 16 | */ 17 | export class SourceDataProvider extends DataProvider { 18 | 19 | /** 20 | * Creates Source tree view items for the currently selected kubernetes cluster. 21 | * @returns Source tree view items to display. 22 | */ 23 | async buildTree(): Promise { 24 | statusBar.startLoadingTree(); 25 | 26 | const treeItems: SourceNode[] = []; 27 | 28 | setVSCodeContext(ContextTypes.LoadingSources, true); 29 | 30 | // Fetch all sources asynchronously and at once 31 | const [gitRepositories, ociRepositories, helmRepositories, buckets, namespaces] = await Promise.all([ 32 | kubernetesTools.getGitRepositories(), 33 | kubernetesTools.getOciRepositories(), 34 | kubernetesTools.getHelmRepositories(), 35 | kubernetesTools.getBuckets(), 36 | kubernetesTools.getNamespaces(), 37 | ]); 38 | 39 | // add git repositories to the tree 40 | if (gitRepositories) { 41 | for (const gitRepository of sortByMetadataName(gitRepositories.items)) { 42 | treeItems.push(new GitRepositoryNode(gitRepository)); 43 | } 44 | } 45 | 46 | // add oci repositories to the tree 47 | if (ociRepositories) { 48 | for (const ociRepository of sortByMetadataName(ociRepositories.items)) { 49 | treeItems.push(new OCIRepositoryNode(ociRepository)); 50 | } 51 | } 52 | 53 | // add helm repositores to the tree 54 | if (helmRepositories) { 55 | for (const helmRepository of sortByMetadataName(helmRepositories.items)) { 56 | treeItems.push(new HelmRepositoryNode(helmRepository)); 57 | } 58 | } 59 | 60 | // add buckets to the tree 61 | if (buckets) { 62 | for (const bucket of sortByMetadataName(buckets.items)) { 63 | treeItems.push(new BucketNode(bucket)); 64 | } 65 | } 66 | 67 | setVSCodeContext(ContextTypes.LoadingSources, false); 68 | setVSCodeContext(ContextTypes.NoSources, treeItems.length === 0); 69 | statusBar.stopLoadingTree(); 70 | 71 | return this.groupByNamespace(namespaces?.items || [], treeItems); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /webview-ui/configureGitOps/src/lib/utils/vscode.ts: -------------------------------------------------------------------------------- 1 | import type { WebviewApi } from 'vscode-webview'; 2 | 3 | /** 4 | * A utility wrapper around the acquireVsCodeApi() function, which enables 5 | * message passing and state management between the webview and extension 6 | * contexts. 7 | * 8 | * This utility also enables webview code to be run in a web browser-based 9 | * dev server by using native web browser features that mock the functionality 10 | * enabled by acquireVsCodeApi. 11 | */ 12 | class VSCodeAPIWrapper { 13 | public readonly vsCodeApi: WebviewApi | undefined; 14 | 15 | constructor() { 16 | // Check if the acquireVsCodeApi function exists in the current development 17 | // context (i.e. VS Code development window or web browser) 18 | if (typeof acquireVsCodeApi === 'function') { 19 | this.vsCodeApi = acquireVsCodeApi(); 20 | } 21 | } 22 | 23 | /** 24 | * Post a message (i.e. send arbitrary data) to the owner of the webview. 25 | * 26 | * @remarks When running webview code inside a web browser, postMessage will instead 27 | * log the given message to the console. 28 | * 29 | * @param message Abitrary data (must be JSON serializable) to send to the extension context. 30 | */ 31 | public postMessage(message: unknown) { 32 | if (this.vsCodeApi) { 33 | this.vsCodeApi.postMessage(message); 34 | } else { 35 | console.log(message); 36 | } 37 | } 38 | 39 | /** 40 | * Get the persistent state stored for this webview. 41 | * 42 | * @remarks When running webview source code inside a web browser, getState will retrieve state 43 | * from local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). 44 | * 45 | * @return The current state or `undefined` if no state has been set. 46 | */ 47 | public getState(): unknown | undefined { 48 | if (this.vsCodeApi) { 49 | return this.vsCodeApi.getState(); 50 | } else { 51 | const state = localStorage.getItem('vscodeState'); 52 | return state ? JSON.parse(state) : undefined; 53 | } 54 | } 55 | 56 | /** 57 | * Set the persistent state stored for this webview. 58 | * 59 | * @remarks When running webview source code inside a web browser, setState will set the given 60 | * state using local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). 61 | * 62 | * @param newState New persisted state. This must be a JSON serializable object. Can be retrieved 63 | * using {@link getState}. 64 | * 65 | * @return The new state. 66 | */ 67 | public setState(newState: T): T { 68 | if (this.vsCodeApi) { 69 | return this.vsCodeApi.setState(newState); 70 | } else { 71 | localStorage.setItem('vscodeState', JSON.stringify(newState)); 72 | return newState; 73 | } 74 | } 75 | } 76 | 77 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi. 78 | export const vscode = new VSCodeAPIWrapper(); 79 | -------------------------------------------------------------------------------- /webview-ui/createFromTemplate/src/lib/utils/vscode.ts: -------------------------------------------------------------------------------- 1 | import type { WebviewApi } from 'vscode-webview'; 2 | 3 | /** 4 | * A utility wrapper around the acquireVsCodeApi() function, which enables 5 | * message passing and state management between the webview and extension 6 | * contexts. 7 | * 8 | * This utility also enables webview code to be run in a web browser-based 9 | * dev server by using native web browser features that mock the functionality 10 | * enabled by acquireVsCodeApi. 11 | */ 12 | class VSCodeAPIWrapper { 13 | public readonly vsCodeApi: WebviewApi | undefined; 14 | 15 | constructor() { 16 | // Check if the acquireVsCodeApi function exists in the current development 17 | // context (i.e. VS Code development window or web browser) 18 | if (typeof acquireVsCodeApi === 'function') { 19 | this.vsCodeApi = acquireVsCodeApi(); 20 | } 21 | } 22 | 23 | /** 24 | * Post a message (i.e. send arbitrary data) to the owner of the webview. 25 | * 26 | * @remarks When running webview code inside a web browser, postMessage will instead 27 | * log the given message to the console. 28 | * 29 | * @param message Abitrary data (must be JSON serializable) to send to the extension context. 30 | */ 31 | public postMessage(message: unknown) { 32 | if (this.vsCodeApi) { 33 | this.vsCodeApi.postMessage(message); 34 | } else { 35 | console.log(message); 36 | } 37 | } 38 | 39 | /** 40 | * Get the persistent state stored for this webview. 41 | * 42 | * @remarks When running webview source code inside a web browser, getState will retrieve state 43 | * from local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). 44 | * 45 | * @return The current state or `undefined` if no state has been set. 46 | */ 47 | public getState(): unknown | undefined { 48 | if (this.vsCodeApi) { 49 | return this.vsCodeApi.getState(); 50 | } else { 51 | const state = localStorage.getItem('vscodeState'); 52 | return state ? JSON.parse(state) : undefined; 53 | } 54 | } 55 | 56 | /** 57 | * Set the persistent state stored for this webview. 58 | * 59 | * @remarks When running webview source code inside a web browser, setState will set the given 60 | * state using local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). 61 | * 62 | * @param newState New persisted state. This must be a JSON serializable object. Can be retrieved 63 | * using {@link getState}. 64 | * 65 | * @return The new state. 66 | */ 67 | public setState(newState: T): T { 68 | if (this.vsCodeApi) { 69 | return this.vsCodeApi.setState(newState); 70 | } else { 71 | localStorage.setItem('vscodeState', JSON.stringify(newState)); 72 | return newState; 73 | } 74 | } 75 | } 76 | 77 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi. 78 | export const vscode = new VSCodeAPIWrapper(); 79 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 2019, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "extends": [ 12 | "plugin:@typescript-eslint/recommended" 13 | ], 14 | "rules": { 15 | "@typescript-eslint/indent": [ 16 | "warn", 17 | "tab", 18 | { 19 | "SwitchCase": 1 20 | } 21 | ], 22 | "@typescript-eslint/semi": "warn", 23 | "@typescript-eslint/naming-convention": [ 24 | "warn", 25 | { 26 | "selector": "enumMember", 27 | "format": [ 28 | "PascalCase", 29 | "UPPER_CASE" 30 | ] 31 | } 32 | ], 33 | "@typescript-eslint/comma-dangle": [ 34 | "warn", 35 | { 36 | "arrays": "always-multiline", 37 | "objects": "always-multiline", 38 | "imports": "never", 39 | "exports": "never", 40 | "functions": "always-multiline", 41 | "enums": "always-multiline" 42 | } 43 | ], 44 | "@typescript-eslint/member-delimiter-style": [ 45 | "warn", 46 | { 47 | "multiline": { 48 | "delimiter": "semi", 49 | "requireLast": true 50 | }, 51 | "singleline": { 52 | "delimiter": "semi", 53 | "requireLast": true 54 | } 55 | } 56 | ], 57 | "@typescript-eslint/array-type": [ 58 | "warn", 59 | { 60 | "default": "array" 61 | } 62 | ], 63 | "@typescript-eslint/type-annotation-spacing": [ 64 | "warn", 65 | { 66 | "before": false, 67 | "after": true 68 | } 69 | ], 70 | "@typescript-eslint/no-shadow": "warn", 71 | "@typescript-eslint/no-non-null-assertion": "off", 72 | "@typescript-eslint/func-call-spacing": "warn", 73 | "@typescript-eslint/prefer-for-of": "warn", 74 | "@typescript-eslint/no-inferrable-types": "warn", 75 | "@typescript-eslint/explicit-module-boundary-types": "off", 76 | "@typescript-eslint/no-empty-function": "off", 77 | "@typescript-eslint/no-unused-vars": "off", 78 | "@typescript-eslint/no-explicit-any": "off", 79 | "@typescript-eslint/ban-ts-comment": "off", 80 | "eol-last": [ 81 | "warn", 82 | "always" 83 | ], 84 | "quotes": [ 85 | "warn", 86 | "single" 87 | ], 88 | "curly": "warn", 89 | "space-infix-ops": "warn", 90 | "prefer-template": "warn", 91 | "arrow-spacing": [ 92 | "warn", 93 | { 94 | "before": true, 95 | "after": true 96 | } 97 | ], 98 | "arrow-parens": [ 99 | "warn", 100 | "as-needed" 101 | ], 102 | "arrow-body-style": [ 103 | "warn", 104 | "as-needed" 105 | ], 106 | "eqeqeq": "warn", 107 | "brace-style": "warn", 108 | "no-throw-literal": "warn", 109 | "prefer-const": "off", 110 | "semi": "off", 111 | "indent": "off", 112 | "comma-dangle": "off", 113 | "no-shadow": "off" 114 | }, 115 | "ignorePatterns": [ 116 | "out", 117 | "dist", 118 | "**/*.d.ts" 119 | ] 120 | } 121 | -------------------------------------------------------------------------------- /src/views/dataProviders/dataProvider.ts: -------------------------------------------------------------------------------- 1 | import { Event, EventEmitter, TreeDataProvider, TreeItem } from 'vscode'; 2 | import { Namespace } from '../../kubernetes/types/kubernetesTypes'; 3 | import { NamespaceNode } from '../nodes/namespaceNode'; 4 | import { TreeNode } from '../nodes/treeNode'; 5 | 6 | /** 7 | * Defines tree view data provider base class for all GitOps tree views. 8 | */ 9 | export class DataProvider implements TreeDataProvider { 10 | private treeItems: TreeItem[] | null = null; 11 | private _onDidChangeTreeData: EventEmitter = new EventEmitter(); 12 | readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; 13 | 14 | /** 15 | * Reloads tree view item and its children. 16 | * @param treeItem Tree item to refresh. 17 | */ 18 | public refresh(treeItem?: TreeItem) { 19 | if (!treeItem) { 20 | // Only clear all root nodes when no node was passed 21 | this.treeItems = null; 22 | } 23 | this._onDidChangeTreeData.fire(treeItem); 24 | } 25 | 26 | /** 27 | * Gets tree view item for the specified tree element. 28 | * @param element Tree element. 29 | * @returns Tree view item. 30 | */ 31 | public getTreeItem(element: TreeItem): TreeItem { 32 | return element; 33 | } 34 | 35 | /** 36 | * Gets tree element parent. 37 | * @param element Tree item to get parent for. 38 | * @returns Parent tree item or null for the top level nodes. 39 | */ 40 | public getParent(element: TreeItem): TreeItem | null { 41 | if (element instanceof TreeNode && element.parent) { 42 | return element.parent; 43 | } 44 | return null; 45 | } 46 | 47 | /** 48 | * Gets children for the specified tree element. 49 | * Creates new tree view items for the root node. 50 | * @param element The tree element to get children for. 51 | * @returns Tree element children or empty array. 52 | */ 53 | public async getChildren(element?: TreeItem): Promise { 54 | if (!this.treeItems) { 55 | this.treeItems = await this.buildTree(); 56 | } 57 | 58 | if (element instanceof TreeNode) { 59 | return element.children; 60 | } 61 | 62 | if (!element && this.treeItems) { 63 | return this.treeItems; 64 | } 65 | 66 | return []; 67 | } 68 | 69 | /** 70 | * Creates initial tree view items collection. 71 | * @returns 72 | */ 73 | buildTree(): Promise { 74 | return Promise.resolve([]); 75 | } 76 | 77 | groupByNamespace(namespaces: Namespace[], nodes: TreeNode[]): NamespaceNode[] { 78 | const namespaceNodes: NamespaceNode[] = []; 79 | 80 | namespaces.forEach(ns => { 81 | const name = ns.metadata.name; 82 | 83 | const nsChildNodes = nodes.filter(node => node.resource?.metadata?.namespace === name); 84 | if(nsChildNodes.length > 0) { 85 | const nsNode = new NamespaceNode(ns); 86 | nsChildNodes.forEach(childNode => nsNode.addChild(childNode)); 87 | namespaceNodes.push(nsNode); 88 | } 89 | }); 90 | 91 | return namespaceNodes; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/commands/createSource.ts: -------------------------------------------------------------------------------- 1 | import gitUrlParse from 'git-url-parse'; 2 | import { commands, env, Uri, window } from 'vscode'; 3 | import { azureTools } from '../azure/azureTools'; 4 | import { CommandId } from '../commands'; 5 | 6 | 7 | /** 8 | * Show notifications reminding users to add a public key 9 | * to the git repository (when the url uses SSH protocol). 10 | */ 11 | export function showDeployKeyNotificationIfNeeded(url: string, deployKey?: string) { 12 | if (!deployKey) { 13 | return; 14 | } 15 | 16 | const parsedGitUrl = gitUrlParse(url); 17 | const isSSH = parsedGitUrl.protocol === 'ssh'; 18 | const isGitHub = isUrlSourceGitHub(parsedGitUrl.source); 19 | const isAzureDevops = isUrlSourceAzureDevops(parsedGitUrl.source); 20 | 21 | if (isSSH && deployKey) { 22 | if (isGitHub) { 23 | showDeployKeysPageNotification(Uri.parse(deployKeysGitHubPage(url))); 24 | } else if (isAzureDevops) { 25 | showDeployKeysPageNotification(Uri.parse(deployKeysAzureDevopsPage(url))); 26 | } 27 | showDeployKeyNotification(deployKey); 28 | } 29 | } 30 | 31 | /** 32 | * Transform an url from `git@github.com:usernamehw/sample-k8s.git` to 33 | * `ssh://git@github.com/usernamehw/sample-k8s` 34 | * @param gitUrl target git url 35 | */ 36 | export function makeSSHUrlFromGitUrl(gitUrl: string): string { 37 | if (gitUrl.startsWith('ssh')) { 38 | return gitUrl; 39 | } 40 | 41 | const parsedGitUrl = gitUrlParse(gitUrl); 42 | 43 | return `ssh://${parsedGitUrl.user}@${parsedGitUrl.resource}${parsedGitUrl.pathname}`; 44 | } 45 | /** 46 | * Make a link to the "Deploy keys" page for 47 | * the provided GitHub repository url. 48 | * @param GitHub repository url 49 | */ 50 | export function deployKeysGitHubPage(repoUrl: string) { 51 | const parsedGitUrl = gitUrlParse(repoUrl); 52 | return `https://github.com/${parsedGitUrl.owner}/${parsedGitUrl.name}/settings/keys`; 53 | } 54 | /** 55 | * Make a link to the "SSH Public Keys" page for 56 | * the provided Azure Devops repository url. 57 | * @param GitHub repository url 58 | */ 59 | export function deployKeysAzureDevopsPage(repoUrl: string) { 60 | const parsedGitUrl = gitUrlParse(repoUrl); 61 | return `https://dev.azure.com/${parsedGitUrl.name}/_usersSettings/keys`; 62 | } 63 | 64 | export async function showDeployKeyNotification(deployKey: string) { 65 | const copyButton = 'Copy'; 66 | const confirm = await window.showInformationMessage(`Add deploy key to the repository: ${deployKey}`, copyButton); 67 | if (confirm === copyButton) { 68 | env.clipboard.writeText(deployKey); 69 | } 70 | } 71 | 72 | export async function showDeployKeysPageNotification(uri: Uri) { 73 | const deployKeysButton = 'Open'; 74 | const confirm = await window.showInformationMessage('Open repository "Deploy keys" page', deployKeysButton); 75 | if (confirm === deployKeysButton) { 76 | commands.executeCommand(CommandId.VSCodeOpen, uri); 77 | } 78 | } 79 | 80 | export function isUrlSourceAzureDevops(urlSource: string) { 81 | return urlSource === 'dev.azure.com'; 82 | } 83 | export function isUrlSourceGitHub(urlSource: string) { 84 | return urlSource === 'github.com'; 85 | } 86 | -------------------------------------------------------------------------------- /src/azure/azurePrereqs.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { ClusterProvider } from '../kubernetes/types/kubernetesTypes'; 3 | import { shell } from '../shell'; 4 | import { AzureClusterProvider } from './azureTools'; 5 | 6 | /** 7 | * Return true if all prerequisites for installing Azure cluster extension 'microsoft.flux' are ready 8 | */ 9 | export async function checkAzurePrerequisites(clusterProvider: AzureClusterProvider): Promise { 10 | if(clusterProvider === ClusterProvider.AKS) { 11 | const results = await Promise.all([ 12 | checkPrerequistesProviders(), 13 | checkPrerequistesCliExtensions(), 14 | checkPrerequistesFeatures(), 15 | ]); 16 | 17 | return (results[0] && results[1] && results[2]); 18 | } else { 19 | const results = await Promise.all([ 20 | checkPrerequistesProviders(), 21 | checkPrerequistesCliExtensions(), 22 | ]); 23 | 24 | return (results[0] && results[1]); 25 | } 26 | } 27 | 28 | async function checkPrerequistesFeatures(): Promise { 29 | const result = await shell.execWithOutput('az feature show --namespace Microsoft.ContainerService -n AKS-ExtensionManager'); 30 | const success = result.stdout.includes('"state": "Registered"'); 31 | 32 | if(!success) { 33 | window.showWarningMessage('Missing Azure Prerequisite: Feature \'Microsoft.ContainerService\AKS-ExtensionManager\'', 'OK'); 34 | } 35 | 36 | return success; 37 | } 38 | 39 | async function checkPrerequistesProviders(): Promise { 40 | const result = await shell.execWithOutput('az provider list -o table'); 41 | const lines = result.stdout.replace(/\r\n/g,'\n').split('\n'); 42 | 43 | let registeredCompontents = 0; 44 | for(let line of lines) { 45 | if(/^Microsoft.Kubernetes\b.*\bRegistered\b/.test(line)) { 46 | registeredCompontents++; 47 | } else { 48 | window.showWarningMessage('Missing Azure Prerequisite: Provider \'Microsoft.Kubernetes\'', 'OK'); 49 | } 50 | if(/^Microsoft.KubernetesConfiguration\b.*\bRegistered\b/.test(line)) { 51 | registeredCompontents++; 52 | } else { 53 | window.showWarningMessage('Missing Azure Prerequisite: Provider \'Microsoft.KubernetesConfiguration\'', 'OK'); 54 | } 55 | if(/^Microsoft.ContainerService\b.*\bRegistered\b/.test(line)) { 56 | registeredCompontents++; 57 | } else { 58 | window.showWarningMessage('Missing Azure Prerequisite: Provider \'Microsoft.ContainerService\'', 'OK'); 59 | } 60 | } 61 | 62 | return registeredCompontents === 3; 63 | } 64 | 65 | async function checkPrerequistesCliExtensions(): Promise { 66 | const result = await shell.execWithOutput('az extension list -o table'); 67 | 68 | const configurationSuccess = result.stdout.includes('k8s-configuration'); 69 | if(!configurationSuccess) { 70 | window.showWarningMessage('Missing Azure Prerequisite: az CLI extension \'k8s-configuration\'', 'OK'); 71 | } 72 | 73 | const extensionSuccess = result.stdout.includes('k8s-extension'); 74 | if(!extensionSuccess) { 75 | window.showWarningMessage('Missing Azure Prerequisite: az CLI extension \'k8s-extension\'', 'OK'); 76 | } 77 | 78 | return configurationSuccess && extensionSuccess; 79 | } 80 | -------------------------------------------------------------------------------- /src/webview-backend/configureGitOps/openWebview.ts: -------------------------------------------------------------------------------- 1 | import { Uri, window, workspace } from 'vscode'; 2 | import { failed } from '../../errorable'; 3 | import { telemetry } from '../../extension'; 4 | import { getExtensionContext } from '../../extensionContext'; 5 | import { getFolderGitInfo, GitInfo } from '../../git/gitInfo'; 6 | import { kubernetesTools } from '../../kubernetes/kubernetesTools'; 7 | import { FluxSourceObject, namespacedObject } from '../../kubernetes/types/flux/object'; 8 | import { ClusterProvider, KubernetesObject } from '../../kubernetes/types/kubernetesTypes'; 9 | import { TelemetryEventNames } from '../../telemetry'; 10 | import { getCurrentClusterInfo } from '../../views/treeViews'; 11 | import { WebviewBackend } from '../WebviewBackend'; 12 | 13 | import { ConfigureGitOpsWebviewParams } from '../types'; 14 | import { receiveMessage } from './receiveMessage'; 15 | 16 | let webview: WebviewBackend | undefined; 17 | 18 | /** 19 | * Open the webview editor with a form to enter all the flags 20 | * needed to create a source (and possibly Kustomization) 21 | */ 22 | export async function openConfigureGitOpsWebview(selectSource: boolean, selectedSource?: FluxSourceObject | string, set?: any, gitInfo?: GitInfo) { 23 | telemetry.send(TelemetryEventNames.CreateSourceOpenWebview); 24 | 25 | const clusterInfo = await getCurrentClusterInfo(); 26 | if (failed(clusterInfo)) { 27 | return; 28 | } 29 | if (clusterInfo.result.clusterProvider === ClusterProvider.Unknown) { 30 | window.showErrorMessage('Cluster provider is not detected '); 31 | return; 32 | } 33 | if (clusterInfo.result.clusterProvider === ClusterProvider.DetectionFailed) { 34 | window.showErrorMessage('Cluster provider detection failed.'); 35 | return; 36 | } 37 | 38 | if (!gitInfo && workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { 39 | // use the first open folder 40 | gitInfo = await getFolderGitInfo(workspace.workspaceFolders[0].uri.fsPath); 41 | } 42 | 43 | const [nsResults, gitResults, ociResults, bucketResults] = await Promise.all([kubernetesTools.getNamespaces(), 44 | kubernetesTools.getGitRepositories(), 45 | kubernetesTools.getOciRepositories(), 46 | kubernetesTools.getBuckets(), 47 | ]); 48 | 49 | const namespaces = nsResults?.items.map(i => i.metadata.name) as string[]; 50 | 51 | const sources: KubernetesObject[] = [...gitResults?.items || [], 52 | ...ociResults?.items || [], 53 | ...bucketResults?.items || []]; 54 | 55 | const selectedSourceName = typeof selectedSource === 'string' ? selectedSource : (namespacedObject(selectedSource) || ''); 56 | 57 | const webviewParams: ConfigureGitOpsWebviewParams = { 58 | clusterInfo: clusterInfo.result, 59 | gitInfo, 60 | namespaces: namespaces, 61 | sources: sources, 62 | selectSourceTab: selectSource, 63 | selectedSource: selectedSourceName, 64 | set, 65 | }; 66 | 67 | 68 | if(!webview || webview.disposed) { 69 | const extensionUri = getExtensionContext().extensionUri; 70 | const uri = Uri.joinPath(extensionUri, 'webview-ui', 'configureGitOps'); 71 | webview = new WebviewBackend('Configure GitOps', uri, webviewParams, receiveMessage); 72 | } else { 73 | webview.update(webviewParams); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/webview-backend/configureGitOps/lib/createAzure.ts: -------------------------------------------------------------------------------- 1 | import { AzureClusterProvider, azureTools, CreateSourceBucketAzureArgs, CreateSourceGitAzureArgs } from '../../../azure/azureTools'; 2 | import { showDeployKeyNotificationIfNeeded } from '../../../commands/createSource'; 3 | import { telemetry } from '../../../extension'; 4 | import { ClusterInfo, KubernetesObjectKinds } from '../../../kubernetes/types/kubernetesTypes'; 5 | import { TelemetryEventNames } from '../../../telemetry'; 6 | import { ParamsDictionary } from '../../../utils/typeUtils'; 7 | import { refreshSourcesTreeView, refreshWorkloadsTreeView } from '../../../views/treeViews'; 8 | 9 | export async function createConfigurationAzure(data: ParamsDictionary) { 10 | const clusterInfo = data.clusterInfo as ClusterInfo; 11 | const source = data.source; 12 | const kustomization = data.kustomization; 13 | 14 | if(source) { 15 | if(source.kind === 'GitRepository') { 16 | createGitSourceAzure(source, kustomization, clusterInfo); 17 | } else if(source.kind === 'Bucket') { 18 | createBucketSourceAzure(source, kustomization, clusterInfo); 19 | } 20 | 21 | } else if(kustomization) { 22 | azureTools.createKustomization(kustomization.name, kustomization.source, kustomization.path, 23 | clusterInfo.contextName, clusterInfo.clusterProvider as AzureClusterProvider, kustomization.dependsOn, kustomization.prune); 24 | } 25 | } 26 | 27 | async function createGitSourceAzure(source: ParamsDictionary, kustomization: ParamsDictionary, clusterInfo: ClusterInfo) { 28 | const args = { 29 | sourceName: source.name, 30 | url: source.url, 31 | ...source, 32 | ...clusterInfo, 33 | kustomizationName: kustomization?.name, 34 | kustomizationPath: kustomization?.path, 35 | kustomizationDependsOn: kustomization?.dependsOn, 36 | kustomizationPrune: kustomization?.prune, 37 | } as CreateSourceGitAzureArgs; 38 | 39 | 40 | telemetry.send(TelemetryEventNames.CreateSource, { 41 | kind: KubernetesObjectKinds.GitRepository, 42 | }); 43 | 44 | const deployKey = await azureTools.createSourceGit(args); 45 | 46 | setTimeout(() => { 47 | // Wait a bit for the repository to have a failed state in case of SSH url 48 | refreshSourcesTreeView(); 49 | refreshWorkloadsTreeView(); 50 | }, 1000); 51 | 52 | showDeployKeyNotificationIfNeeded(args.url, deployKey); 53 | } 54 | 55 | 56 | async function createBucketSourceAzure(source: ParamsDictionary, kustomization: ParamsDictionary, clusterInfo: ClusterInfo) { 57 | telemetry.send(TelemetryEventNames.CreateSource, { 58 | kind: KubernetesObjectKinds.Bucket, 59 | }); 60 | 61 | const args: any = { 62 | sourceName: source.name, 63 | url: source.endpoint, 64 | configurationName: source.name, 65 | bucketName: source.bucketName, 66 | sourceScope: source.azureScope, 67 | sourceNamespace: source.namespace, 68 | ...source, 69 | ...clusterInfo, 70 | kustomizationName: kustomization?.name, 71 | kustomizationPath: kustomization?.name, 72 | kustomizationDependsOn: kustomization?.name, 73 | kustomizationPrune: kustomization?.prune, 74 | } as CreateSourceGitAzureArgs; 75 | 76 | await azureTools.createSourceBucket(args); 77 | 78 | setTimeout(() => { 79 | refreshSourcesTreeView(); 80 | refreshWorkloadsTreeView(); 81 | }, 1000); 82 | } 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/kubernetes/types/flux/bucket.ts: -------------------------------------------------------------------------------- 1 | import { Artifact, DeploymentCondition, KubernetesObject, KubernetesObjectKinds, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; 2 | 3 | /** 4 | * Buckets result from running 5 | * `kubectl get Bucket -A` command. 6 | */ 7 | export interface BucketResult { 8 | readonly apiVersion: string; 9 | readonly kind: KubernetesObjectKinds.List; 10 | readonly items: Bucket[]; 11 | readonly metadata: ResultMetadata; 12 | } 13 | 14 | /** 15 | * Bucket info object. 16 | */ 17 | export interface Bucket extends KubernetesObject { 18 | 19 | // standard kubernetes object fields 20 | readonly apiVersion: string; 21 | readonly kind: KubernetesObjectKinds.Bucket; 22 | readonly metadata: ObjectMeta; 23 | 24 | /** 25 | * Bucket spec details. 26 | * 27 | * @see https://github.com/fluxcd/source-controller/blob/main/docs/api/source.md#bucketspec 28 | */ 29 | readonly spec: { 30 | 31 | /** 32 | * The S3 compatible storage provider name, default ('generic') 33 | */ 34 | readonly provider?: string; 35 | 36 | /** 37 | * The bucket name 38 | */ 39 | readonly bucketName: string; 40 | 41 | /** 42 | * The bucket endpoint address 43 | */ 44 | readonly endpoint: string; 45 | 46 | /** 47 | * Insecure allows connecting to a non-TLS S3 HTTP endpoint 48 | */ 49 | readonly insecure?: boolean; 50 | 51 | /** 52 | * The bucket region 53 | */ 54 | readonly region?: string; 55 | 56 | /** 57 | * The name of the secret containing authentication credentials for the Bucket 58 | */ 59 | readonly secretRef?: { name?: string; }; 60 | 61 | /** 62 | * The interval at which to check for bucket updates 63 | */ 64 | readonly interval: string; 65 | 66 | /** 67 | * The timeout for download operations, defaults to 20s 68 | */ 69 | readonly timeout?: string; 70 | 71 | /** 72 | * Ignore overrides the set of excluded patterns in the .sourceignore format 73 | * (which is the same as .gitignore). If not provided, a default will be used, 74 | * consult the documentation for your version to find out what those are. 75 | */ 76 | readonly ignore?: string; 77 | 78 | /** 79 | * This flag tells the controller to suspend the reconciliation of this source 80 | */ 81 | readonly suspend?: boolean; 82 | }; 83 | 84 | /** 85 | * Bucket source status info. 86 | * 87 | * @see https://github.com/fluxcd/source-controller/blob/main/docs/api/source.md#bucketstatus 88 | */ 89 | readonly status: { 90 | 91 | /** 92 | * ObservedGeneration is the last observed generation 93 | */ 94 | readonly observedGeneration?: number; 95 | 96 | /** 97 | * Conditions holds the conditions for the Bucket 98 | */ 99 | readonly conditions?: DeploymentCondition[]; 100 | 101 | /** 102 | * URL is the download link for the artifact output of the last Bucket sync 103 | */ 104 | readonly url?: string; 105 | 106 | /** 107 | * Artifact represents the output of the last successful Bucket sync 108 | */ 109 | readonly artifact?: Artifact; 110 | 111 | /** 112 | * LastHandledReconcileAt is the last manual reconciliation request 113 | * (by annotating the Bucket) handled by the reconciler. 114 | */ 115 | readonly lastHandledReconcileAt?: string; 116 | }; 117 | } 118 | -------------------------------------------------------------------------------- /src/kubernetes/types/flux/helmRepository.ts: -------------------------------------------------------------------------------- 1 | import { Artifact, DeploymentCondition, KubernetesObject, KubernetesObjectKinds, LocalObjectReference, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; 2 | 3 | /** 4 | * Helm repositories result from running 5 | * `kubectl get HelmRepository -A` command. 6 | */ 7 | export interface HelmRepositoryResult { 8 | readonly apiVersion: string; 9 | readonly kind: KubernetesObjectKinds.List; 10 | readonly items: HelmRepository[]; 11 | readonly metadata: ResultMetadata; 12 | } 13 | 14 | /** 15 | * Helm repository info object. 16 | */ 17 | export interface HelmRepository extends KubernetesObject { 18 | 19 | // standard kubernetes object fields 20 | readonly apiVersion: string; 21 | readonly kind: KubernetesObjectKinds.HelmRepository; 22 | readonly metadata: ObjectMeta; 23 | 24 | /** 25 | * Helm repository spec details. 26 | * 27 | * @see https://github.com/fluxcd/source-controller/blob/main/docs/api/source.md#helmrepositoryspec 28 | */ 29 | readonly spec: { 30 | 31 | /** 32 | * The Helm repository URL, a valid URL contains at least a protocol and host 33 | */ 34 | readonly url: string; 35 | 36 | /** 37 | * The name of the secret containing authentication credentials for the Helm repository. 38 | * For HTTP/S basic auth the secret must contain username and password fields. 39 | * For TLS the secret must contain a certFile and keyFile, and/or caCert fields. 40 | */ 41 | readonly secretRef?: LocalObjectReference; 42 | 43 | /** 44 | * PassCredentials allows the credentials from the SecretRef 45 | * to be passed on to a host that does not match the host as defined in URL. 46 | * This may be required if the host of the advertised chart URLs in the index 47 | * differ from the defined URL. Enabling this should be done with caution, 48 | * as it can potentially result in credentials getting stolen in a MITM-attack. 49 | */ 50 | readonly passCredentials?: boolean; 51 | 52 | /** 53 | * The interval at which to check the upstream for updates 54 | */ 55 | readonly interval: string; 56 | 57 | /** 58 | * The timeout of index downloading, defaults to 60s 59 | */ 60 | readonly timeout?: string; 61 | 62 | /** 63 | * The type of HelmRepository, can be set to `oci` or `default` 64 | * (May be missing from older Flux installs) 65 | */ 66 | readonly type?: string; 67 | 68 | /** 69 | * This flag tells the controller to suspend the reconciliation of this source 70 | */ 71 | readonly suspend?: boolean; 72 | }; 73 | 74 | /** 75 | * Helm repository status info. 76 | * 77 | * @see https://github.com/fluxcd/source-controller/blob/main/docs/api/source.md#helmrepositorystatus 78 | */ 79 | readonly status: { 80 | 81 | /** 82 | * ObservedGeneration is the last observed generation 83 | */ 84 | readonly observedGeneration?: number; 85 | 86 | /** 87 | * Conditions holds the conditions for the HelmRepository 88 | */ 89 | readonly conditions?: DeploymentCondition[]; 90 | 91 | /** 92 | * URL is the download link for the last index fetched 93 | */ 94 | readonly url?: string; 95 | 96 | /** 97 | * Artifact represents the output of the last successful repository sync 98 | */ 99 | readonly artifact?: Artifact; 100 | }; 101 | } 102 | --------------------------------------------------------------------------------