diff --git a/.github/workflows/build-vsix.yml b/.github/workflows/build-vsix.yml index 2141aed7..1f8ce6bb 100644 --- a/.github/workflows/build-vsix.yml +++ b/.github/workflows/build-vsix.yml @@ -64,17 +64,17 @@ jobs: fi; - name: Clone Repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3 + uses: actions/checkout@dc323e67f16fb5f7663d20ff7941f27f5809e9b6 # pin@v2 with: fetch-depth: 0 - name: Setup Flux CLI - uses: fluxcd/flux2/action@1730f3c46bddf0a29787d8d4fa5ace283f298e49 # pin@main + uses: fluxcd/flux2/action@2c7d650d4472bb7c92fa5fea86589527dfa5abcc # pin@main with: token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node version - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # pin@v3 + uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # pin@v1 with: node-version: 19 @@ -96,11 +96,11 @@ jobs: - name: Setup Kubernetes uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 # pin@v0.5.0 with: - version: v0.20.0 - image: kindest/node:v1.28.0 + version: v0.17.0 + image: kindest/node:v1.23.13 - name: Run Tests - uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # pin@v1 + uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d # pin@v1 with: run: npm test options: "-screen 0 1600x1200x24" @@ -167,7 +167,7 @@ jobs: if: ${{ github.event.inputs.publishMarketplace == 'yes' && github.event.inputs.releaseChannel == 'stable' }} - name: Publish to Open VSX Registry (Edge) - uses: HaaLeo/publish-vscode-extension@dfe4f6ad46624424fe24cb5bca79839183399045 # pin@v1 + uses: HaaLeo/publish-vscode-extension@c1a0486c5a3eed24e8c21d4e37889a7c4c60c443 # pin@v1 if: ${{ github.event.inputs.publishOpenVSX == 'yes' && github.event.inputs.releaseChannel == 'edge' }} with: preRelease: true @@ -175,7 +175,7 @@ jobs: extensionFile: ./gitops-tools-${{ env.RELEASE_VERSION }}.vsix - name: Publish to Open VSX Registry (Stable) - uses: HaaLeo/publish-vscode-extension@dfe4f6ad46624424fe24cb5bca79839183399045 # pin@v1 + uses: HaaLeo/publish-vscode-extension@c1a0486c5a3eed24e8c21d4e37889a7c4c60c443 # pin@v1 if: ${{ github.event.inputs.publishOpenVSX == 'yes' && github.event.inputs.releaseChannel == 'stable' }} with: preRelease: false @@ -199,7 +199,7 @@ jobs: echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV - name: GitHub Release - uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # pin@v1 + uses: ncipollo/release-action@a2e71bdd4e7dab70ca26a852f29600c98b33153e # pin@v1 with: artifacts: "./gitops-tools-*" bodyFile: ${{ github.workspace }}-CHANGELOG.txt @@ -228,14 +228,14 @@ jobs: Feature branches should still be squashed, but \`release-pr\` must always be merged to complete the release. - See the [CONTRIBUTING.md](https://github.com/weaveworks/vscode-gitops-tools/blob/main/CONTRIBUTING.md#releasing) for more information about the release process. + See the [DEVELOPMENT.md](https://github.com/weaveworks/vscode-gitops-tools/blob/main/DEVELOPMENT.md#releasing) for more information about the release process. pr_reviewer: ${{ github.actor }} pr_draft: false github_token: ${{ secrets.GITHUB_TOKEN }} # - name: Get the version # id: version -# run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT +# run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} # shell: bash # # - uses: apexskier/github-semver-parse@671ddf80785e4d721e76723ec1e687317f85bfe9 # pin@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d2f6b1e..cf774c71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,27 +11,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3 - - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # pin@v3 + - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # pin@v3 with: - node-version: '20' + node-version: '19' - run: npm install - name: Setup Kubernetes uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 # pin@v0.5.0 with: - version: v0.20.0 - image: kindest/node:v1.28.0 + version: v0.17.0 + image: kindest/node:v1.23.13 - name: Setup Flux CLI - uses: fluxcd/flux2/action@1730f3c46bddf0a29787d8d4fa5ace283f298e49 # pin@main + uses: fluxcd/flux2/action@2c7d650d4472bb7c92fa5fea86589527dfa5abcc # pin@main with: token: ${{ secrets.GITHUB_TOKEN }} - name: extension test - uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # pin@v1 + uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d # pin@v1 with: run: 'npm test' options: "-screen 0 1600x1200x24" diff --git a/.vscode/settings.json b/.vscode/settings.json index 129a253b..5adc2423 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,9 +11,5 @@ }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts "typescript.tsc.autoDetect": "off", - "debug.node.autoAttach": "on", - "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": true - }, + "debug.node.autoAttach": "on" } \ No newline at end of file diff --git a/CONTRIBUTING.md b/DEVELOPMENT.md similarity index 100% rename from CONTRIBUTING.md rename to DEVELOPMENT.md diff --git a/README.md b/README.md index 07dd4c53..b3a4a95c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GitOps Tools for Visual Studio Code -[![VSCode Marketplace Link](https://img.shields.io/visual-studio-marketplace/v/weaveworks.vscode-gitops-tools)](https://marketplace.visualstudio.com/items?itemName=Weaveworks.vscode-gitops-tools) -[![Install Counter](https://img.shields.io/visual-studio-marketplace/i/weaveworks.vscode-gitops-tools)](https://marketplace.visualstudio.com/items?itemName=Weaveworks.vscode-gitops-tools) +[![VSCode Marketplace Link](https://vsmarketplacebadges.dev/version-short/weaveworks.vscode-gitops-tools.png)](https://marketplace.visualstudio.com/items?itemName=Weaveworks.vscode-gitops-tools) +[![Install Counter](https://vsmarketplacebadges.dev/installs/weaveworks.vscode-gitops-tools.png)](https://marketplace.visualstudio.com/items?itemName=Weaveworks.vscode-gitops-tools) Weaveworks [GitOps Tools Extension](https://marketplace.visualstudio.com/items?itemName=Weaveworks.vscode-gitops-tools) provides an intuitive way to manage, troubleshoot and operate your Kubernetes environment following the GitOps operating model. GitOps accelerates your development lifecycle and simplifies your continuous delivery pipelines. The extension is built on Flux (a CNCF open source project). To learn more about the Flux GitOps toolkit, visit [fluxcd.io] @@ -27,15 +27,12 @@ There are a few requirements before installing and using the extension: Once you have satisfied these requirements you can find and install GitOps Tools in the [Extension Marketplace](https://marketplace.visualstudio.com/items?itemName=Weaveworks.vscode-gitops-tools) by searching for "**fluxcd**" or "**gitops**". -### kubectl proxy -This extension uses two different methods to get information from the Kubernetes cluster. It preferentially will run `kubectl proxy -p 0` for your selected cluster and will use the proxy with a javascript client for faster performance and real-time updates. This also requires `watch` RBAC for Flux resources. If the proxy client connection can't be established the extension will fall back to `kubectl get` for querying the cluster. # Features - Configure, visualize and manage Flux resources - Tree views for Clusters, Sources, and Workloads -- Observe Flux resource updates in the cluster in real-time - Select clusters and examine installed [GitOps Toolkit components](https://fluxcd.io/docs/components/) - Enable and Disable GitOps (install/uninstall Flux) on clusters - Create, view and edit sources (git, OCI, Helm and Bucket), and workloads (Kustomization and HelmRelease) @@ -45,7 +42,6 @@ This extension uses two different methods to get information from the Kubernetes - Clone GitRepository source to user machine and open them in the editor - Preview sources, workloads and other objects information with tooltips - Open remote resources as `.yaml` files in the editor -- Open and edit the kubeconfig file - Trace Kubernetes objects created by workloads - Watch Flux controller logs and `flux` CLI commands for diagnostics - Documentation links for [Flux](https://fluxcd.io/docs) and [Weave GitOps](https://docs.gitops.weave.works/docs/intro/) embedded in the extension @@ -125,37 +121,17 @@ The GitOps Tools Extension depends on the [Kubernetes Tools](https://marketplace - Make sure you have [successfully authenticated](https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli) on your `az` CLI and have access to the [correct subscription](https://docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az_account_set) for your AKS or ARC cluster. - The easiest way to get your AKS or Arc cluster visible by the GitOps and Kubernetes Extensions, is to use the `az` CLI to merge the kubeconfig for accessing your cluster onto the default `kubectl` config. Use `get-credentials` as shown in the [official CLI documentation](https://docs.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az_aks_get_credentials). In order to enable GitOps in a cluster you will likely need the `--admin` credentials. -## Weave GitOps Enterprise (WGE) Integration +## Weave GitOps Enterprise (WGE) Templates -WGE users can access GitOpsTemplates directly from this extensions. WGE integration adds another treeview that shows `GitOpsTemplate`, `Canary`, `Pipeline`, and `GitOpsSet` resources and adds new interactions to each type of resource. All WGE resources have a right-click action to open them in WGE portal. +WGE users can access GitOpsTemplates directly from this extensions. Templates are provided by cluster administrators (Platform Teams) and can be used to quickly create cluster and configure applications with GitOps. -GitOpsTemplates are provided by cluster administrators (Platform Teams) and can be used to quickly create cluster and configure applications with GitOps. Flagger Canaries status can be visualized and their progress tracked. Pipelines are listed with their targets and each `GitopsCluster` attached to a Pipeline can be set as the current selected cluster for quick navigation between clusters. +Templates are an opt-in feature that must be enabled in setting: -WGE integration is an opt-in feature that must be enabled in settings: +![Enable GitOpsTemplates](docs/images/vscode-templates-config.png) -![Enable WGE Features](docs/images/config-enable-wge.png) - -![Weave GitOps Treeview](docs/images/weave-gitops-treeview.png) - -![Create GitOps Template](docs/images/vscode-templates-view.png) - -### WGE Configuration - -For the integration to work, this extension needs a ConfigMap that provides WGE information and settings: - -`kubectl create configmap weave-gitops-interop --from-literal=portalUrl='https://WGE-CLUSTER-HOST' --from-literal=wgeClusterName='WGE-CLUSTER-NAME' -n flux-system`` - -``` -apiVersion: v1 -kind: ConfigMap -metadata: - namespace: flux-system - name: weave-gitops-interop -data: - portalUrl: https://mccp.howard.moomboo.space - wgeClusterName: howard-moomboo-space -``` +After that they can be seen in a new 'Templates' view. Right-click a template to use it: +![Use GitOpsTemplates](docs/images/vscode-templates-view.png) @@ -169,15 +145,10 @@ We rely on the Kubernetes extension to discover and connect to clusters. If you Confirm that your configuration context shows in a terminal running `kubectl config get-contexts` -### _Switching from an unreachable cluster context to a working cluster_ -Unreachable or laggy clusters can create long running that cluster resource queries that finish after switching to a working cluster context. This can lead to the slow cluster data overwriting the current cluster treeview. **Clusters** -> **Refresh** button will reinitialize the views with current data. Timeout settings can be adjusted under **GitOps** section in VSCode Settings. -### _Timeouts and flux check warnings_ -The extension has timeout options that can make be adjusted in VSCode settings to match your cluster network constraints. The default timeout for any data query operation is 60 seconds. -`flux check` command can also be disabled in the settings. This will stop `flux check` warnings about old versions but will disable Flux controller status icons in the the Clusters treeview. # Data and Telemetry diff --git a/docs/images/config-enable-wge.png b/docs/images/config-enable-wge.png deleted file mode 100644 index 5c943227..00000000 Binary files a/docs/images/config-enable-wge.png and /dev/null differ diff --git a/docs/images/vscode-templates-config.png b/docs/images/vscode-templates-config.png new file mode 100644 index 00000000..503016ef Binary files /dev/null and b/docs/images/vscode-templates-config.png differ diff --git a/docs/images/weave-gitops-treeview.png b/docs/images/weave-gitops-treeview.png deleted file mode 100644 index 53a5874e..00000000 Binary files a/docs/images/weave-gitops-treeview.png and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 353759a2..b08e40bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,42 +1,31 @@ { "name": "vscode-gitops-tools", - "version": "0.26.0", + "version": "0.24.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-gitops-tools", - "version": "0.26.0", + "version": "0.24.3", "license": "MPL-2.0", "dependencies": { - "@kubernetes/client-node": "^0.18.1", - "@types/ws": "^8.5.4", - "@vscode/extension-telemetry": "^0.6.0", - "bufferutil": "^4.0.7", + "@kubernetes/client-node": "^0.16.2", + "@vscode/extension-telemetry": "^0.4.7", "change-case": "^4.1.2", "extract-zip": "^2.0.1", "git-url-parse": "^13.0.0", - "is-running": "^2.1.0", "jose": ">=2.0.6", - "lite-deep-equal": "^1.0.6", "parse-path": ">=5.0.0", "parse-url": ">=8.1.0", - "semver": "^7.5.2", + "semver": "^7.3.5", "shell-escape-tag": "^2.0.2", "shelljs": "^0.8.5", "tinytim": "^0.1.1", - "tough-cookie": ">=4.1.3", - "tree-kill": "^1.2.2", - "utf-8-validate": "^6.0.3", "uuid": "^9.0.0", - "vite": ">=2.9.16", - "vscode-kubernetes-tools-api": "^1.3.0", - "vscode-uri": "^3.0.7", - "word-wrap": ">=1.2.4" + "vscode-kubernetes-tools-api": "^1.3.0" }, "devDependencies": { "@types/git-url-parse": "^9.0.1", - "@types/is-running": "^2.1.0", "@types/mocha": "^9.1.0", "@types/node": "14.x", "@types/semver": "^7.3.9", @@ -51,18 +40,14 @@ "eslint": "^8.11.0", "glob": "^7.2.0", "mocha": "^9.2.2", - "tough-cookie": ">=4.1.3", "ts-loader": "^9.2.8", - "tsconfig-paths-webpack-plugin": "^4.0.1", "typescript": "^4.5.5", - "vite": ">=2.9.16", "webpack": "^5.70.0", - "webpack-cli": "^4.9.2", - "word-wrap": ">=1.2.4" + "webpack-cli": "^4.9.2" }, "engines": { "npm": ">=7.0.0", - "vscode": "^1.82.0" + "vscode": "^1.63.0" } }, "node_modules/@discoveryjs/json-ext": { @@ -74,358 +59,6 @@ "node": ">=10.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.14.tgz", - "integrity": "sha512-blODaaL+lngG5bdK/t4qZcQvq2BBqrABmYwqPPcS5VRxrCSGHb9R/rA3fqxh7R18I7WU4KKv+NYkt22FDfalcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.14.tgz", - "integrity": "sha512-rZ2v+Luba5/3D6l8kofWgTnqE+qsC/L5MleKIKFyllHTKHrNBMqeRCnZI1BtRx8B24xMYxeU32iIddRQqMsOsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.14.tgz", - "integrity": "sha512-qSwh8y38QKl+1Iqg+YhvCVYlSk3dVLk9N88VO71U4FUjtiSFylMWK3Ugr8GC6eTkkP4Tc83dVppt2n8vIdlSGg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.14.tgz", - "integrity": "sha512-9Hl2D2PBeDYZiNbnRKRWuxwHa9v5ssWBBjisXFkVcSP5cZqzZRFBUWEQuqBHO4+PKx4q4wgHoWtfQ1S7rUqJ2Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.14.tgz", - "integrity": "sha512-ZnI3Dg4ElQ6tlv82qLc/UNHtFsgZSKZ7KjsUNAo1BF1SoYDjkGKHJyCrYyWjFecmXpvvG/KJ9A/oe0H12odPLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.14.tgz", - "integrity": "sha512-h3OqR80Da4oQCIa37zl8tU5MwHQ7qgPV0oVScPfKJK21fSRZEhLE4IIVpmcOxfAVmqjU6NDxcxhYaM8aDIGRLw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.14.tgz", - "integrity": "sha512-ha4BX+S6CZG4BoH9tOZTrFIYC1DH13UTCRHzFc3GWX74nz3h/N6MPF3tuR3XlsNjMFUazGgm35MPW5tHkn2lzQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.14.tgz", - "integrity": "sha512-5+7vehI1iqru5WRtJyU2XvTOvTGURw3OZxe3YTdE9muNNIdmKAVmSHpB3Vw2LazJk2ifEdIMt/wTWnVe5V98Kg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.14.tgz", - "integrity": "sha512-IXORRe22In7U65NZCzjwAUc03nn8SDIzWCnfzJ6t/8AvGx5zBkcLfknI+0P+hhuftufJBmIXxdSTbzWc8X/V4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.14.tgz", - "integrity": "sha512-BfHlMa0nibwpjG+VXbOoqJDmFde4UK2gnW351SQ2Zd4t1N3zNdmUEqRkw/srC1Sa1DRBE88Dbwg4JgWCbNz/FQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.14.tgz", - "integrity": "sha512-j2/Ex++DRUWIAaUDprXd3JevzGtZ4/d7VKz+AYDoHZ3HjJzCyYBub9CU1wwIXN+viOP0b4VR3RhGClsvyt/xSw==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.14.tgz", - "integrity": "sha512-qn2+nc+ZCrJmiicoAnJXJJkZWt8Nwswgu1crY7N+PBR8ChBHh89XRxj38UU6Dkthl2yCVO9jWuafZ24muzDC/A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.14.tgz", - "integrity": "sha512-aGzXzd+djqeEC5IRkDKt3kWzvXoXC6K6GyYKxd+wsFJ2VQYnOWE954qV2tvy5/aaNrmgPTb52cSCHFE+Z7Z0yg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.14.tgz", - "integrity": "sha512-8C6vWbfr0ygbAiMFLS6OPz0BHvApkT2gCboOGV76YrYw+sD/MQJzyITNsjZWDXJwPu9tjrFQOVG7zijRzBCnLw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.14.tgz", - "integrity": "sha512-G/Lf9iu8sRMM60OVGOh94ZW2nIStksEcITkXdkD09/T6QFD/o+g0+9WVyR/jajIb3A0LvBJ670tBnGe1GgXMgw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.14.tgz", - "integrity": "sha512-TBgStYBQaa3EGhgqIDM+ECnkreb0wkcKqL7H6m+XPcGUoU4dO7dqewfbm0mWEQYH3kzFHrzjOFNpSAVzDZRSJw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.14.tgz", - "integrity": "sha512-stvCcjyCQR2lMTroqNhAbvROqRjxPEq0oQ380YdXxA81TaRJEucH/PzJ/qsEtsHgXlWFW6Ryr/X15vxQiyRXVg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.14.tgz", - "integrity": "sha512-apAOJF14CIsN5ht1PA57PboEMsNV70j3FUdxLmA2liZ20gEQnfTG5QU0FhENo5nwbTqCB2O3WDsXAihfODjHYw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.14.tgz", - "integrity": "sha512-fYRaaS8mDgZcGybPn2MQbn1ZNZx+UXFSUoS5Hd2oEnlsyUcr/l3c6RnXf1bLDRKKdLRSabTmyCy7VLQ7VhGdOQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.14.tgz", - "integrity": "sha512-1c44RcxKEJPrVj62XdmYhxXaU/V7auELCmnD+Ri+UCt+AGxTvzxl9uauQhrFso8gj6ZV1DaORV0sT9XSHOAk8Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.14.tgz", - "integrity": "sha512-EXAFttrdAxZkFQmpvcAQ2bywlWUsONp/9c2lcfvPUhu8vXBBenCXpoq9YkUvVP639ld3YGiYx0YUQ6/VQz3Maw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.14.tgz", - "integrity": "sha512-K0QjGbcskx+gY+qp3v4/940qg8JitpXbdxFhRDA1aYoNaPff88+aEwoq45aqJ+ogpxQxmU0ZTjgnrQD/w8iiUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -525,83 +158,61 @@ } }, "node_modules/@kubernetes/client-node": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.18.1.tgz", - "integrity": "sha512-F3JiK9iZnbh81O/da1tD0h8fQMi/MDttWc/JydyUVnjPEom55wVfnpl4zQ/sWD4uKB8FlxYRPiLwV2ZXB+xPKw==", + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.16.3.tgz", + "integrity": "sha512-L7IckuyuPfhd+/Urib8MRas9D6sfKEq8IaITYcaE6LlU+Y8MeD7MTbuW6Yb2WdeRuFN8HPSS47mxPnOUNYBXEg==", "dependencies": { "@types/js-yaml": "^4.0.1", - "@types/node": "^18.11.17", + "@types/node": "^10.12.0", "@types/request": "^2.47.1", - "@types/ws": "^8.5.3", + "@types/stream-buffers": "^3.0.3", + "@types/tar": "^4.0.3", + "@types/underscore": "^1.8.9", + "@types/ws": "^6.0.1", "byline": "^5.0.0", - "isomorphic-ws": "^5.0.0", + "execa": "5.0.0", + "isomorphic-ws": "^4.0.1", "js-yaml": "^4.1.0", - "jsonpath-plus": "^7.2.0", + "jsonpath-plus": "^0.19.0", + "openid-client": "^4.1.1", "request": "^2.88.0", "rfc4648": "^1.3.0", + "shelljs": "^0.8.5", "stream-buffers": "^3.0.2", "tar": "^6.1.11", "tmp-promise": "^3.0.2", - "tslib": "^2.4.1", - "underscore": "^1.13.6", - "ws": "^8.11.0" - }, - "optionalDependencies": { - "openid-client": "^5.3.0" + "tslib": "^1.9.3", + "underscore": "^1.9.1", + "ws": "^7.3.1" } }, "node_modules/@kubernetes/client-node/node_modules/@types/node": { - "version": "18.16.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz", - "integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==" + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" }, - "node_modules/@kubernetes/client-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - }, - "node_modules/@microsoft/1ds-core-js": { - "version": "3.2.13", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.13.tgz", - "integrity": "sha512-CluYTRWcEk0ObG5EWFNWhs87e2qchJUn0p2D21ZUa3PWojPZfPSBs4//WIE0MYV8Qg1Hdif2ZTwlM7TbYUjfAg==", - "dependencies": { - "@microsoft/applicationinsights-core-js": "2.8.15", - "@microsoft/applicationinsights-shims": "^2.0.2", - "@microsoft/dynamicproto-js": "^1.1.7" - } - }, - "node_modules/@microsoft/1ds-post-js": { - "version": "3.2.13", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.13.tgz", - "integrity": "sha512-HgS574fdD19Bo2vPguyznL4eDw7Pcm1cVNpvbvBLWiW3x4e1FCQ3VMXChWnAxCae8Hb0XqlA2sz332ZobBavTA==", - "dependencies": { - "@microsoft/1ds-core-js": "3.2.13", - "@microsoft/applicationinsights-shims": "^2.0.2", - "@microsoft/dynamicproto-js": "^1.1.7" - } - }, - "node_modules/@microsoft/applicationinsights-core-js": { - "version": "2.8.15", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.15.tgz", - "integrity": "sha512-yYAs9MyjGr2YijQdUSN9mVgT1ijI1FPMgcffpaPmYbHAVbQmF7bXudrBWHxmLzJlwl5rfep+Zgjli2e67lwUqQ==", + "node_modules/@kubernetes/client-node/node_modules/execa": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dependencies": { - "@microsoft/applicationinsights-shims": "2.0.2", - "@microsoft/dynamicproto-js": "^1.1.9" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, - "peerDependencies": { - "tslib": "*" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@microsoft/applicationinsights-shims": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz", - "integrity": "sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==" - }, - "node_modules/@microsoft/dynamicproto-js": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", - "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -637,6 +248,36 @@ "node": ">= 8" } }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -646,6 +287,17 @@ "node": ">= 6" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "node_modules/@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", @@ -693,11 +345,10 @@ "@types/node": "*" } }, - "node_modules/@types/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-y1JGY9nExw7elDF/PUGqV4wQi7PVqa4hsxHf2fXLpc0jpD5kzx5BRldqeuqJQAuctnbsIqWxzYoGxSayvgynBQ==", - "dev": true + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, "node_modules/@types/js-yaml": { "version": "4.0.3", @@ -710,12 +361,28 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", "dev": true }, + "node_modules/@types/minipass": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-3.1.0.tgz", + "integrity": "sha512-b2yPKwCrB8x9SB65kcCistMoe3wrYnxxt5rJSZ1kprw0uOXvhuKi9kTQ746Y+Pbqoh+9C0N4zt0ztmTnG9yg7A==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", @@ -738,6 +405,14 @@ "form-data": "^2.5.0" } }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", @@ -754,11 +429,33 @@ "@types/node": "*" } }, + "node_modules/@types/stream-buffers": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.4.tgz", + "integrity": "sha512-qU/K1tb2yUdhXkLIATzsIPwbtX6BpZk0l3dPW6xqWyhfzzM1ECaQ/8faEnu3CNraLiQ9LHyQQPBGp7N9Fbs25w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tar": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.5.tgz", + "integrity": "sha512-cgwPhNEabHaZcYIy5xeMtux2EmYBitfqEceBUi2t5+ETy4dW6kswt6WX4+HqLeiiKOo42EXbGiDmVJ2x+vi37Q==", + "dependencies": { + "@types/minipass": "*", + "@types/node": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" }, + "node_modules/@types/underscore": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.3.tgz", + "integrity": "sha512-Fl1TX1dapfXyDqFg2ic9M+vlXRktcPJrc4PR7sRc7sdVrjavg/JHlbUXBt8qWWqhJrmSqg3RNAkAPRiOYw6Ahw==" + }, "node_modules/@types/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", @@ -784,9 +481,9 @@ "dev": true }, "node_modules/@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", + "integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==", "dependencies": { "@types/node": "*" } @@ -991,13 +688,9 @@ "dev": true }, "node_modules/@vscode/extension-telemetry": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz", - "integrity": "sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w==", - "dependencies": { - "@microsoft/1ds-core-js": "^3.2.3", - "@microsoft/1ds-post-js": "^3.2.3" - }, + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.4.7.tgz", + "integrity": "sha512-tXjChgxFN6EAfa6LIy/PE3fIbIvj0BnELUmGAG+8tUpsLY3g2iACcVM/UJJXtsU6db29tMqCLITUX2Dd3N06GA==", "engines": { "vscode": "^1.60.0" } @@ -1223,15 +916,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -1253,6 +937,18 @@ "node": ">= 6.0.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1508,18 +1204,6 @@ "node": ">=0.2.0" } }, - "node_modules/bufferutil": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", - "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", @@ -1528,6 +1212,45 @@ "node": ">=0.10.0" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1689,6 +1412,14 @@ "node": ">=6.0" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "engines": { + "node": ">=6" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -1714,6 +1445,14 @@ "node": ">=6" } }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1784,7 +1523,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1833,12 +1571,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1963,43 +1734,6 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, - "node_modules/esbuild": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.14.tgz", - "integrity": "sha512-uNPj5oHPYmj+ZhSQeYQVFZ+hAlJZbAGOmmILWIqrGvPVlNLbyOvU5Bu6Woi8G8nskcx0vwY0iFoMPrzT86Ko+w==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.14", - "@esbuild/android-arm64": "0.18.14", - "@esbuild/android-x64": "0.18.14", - "@esbuild/darwin-arm64": "0.18.14", - "@esbuild/darwin-x64": "0.18.14", - "@esbuild/freebsd-arm64": "0.18.14", - "@esbuild/freebsd-x64": "0.18.14", - "@esbuild/linux-arm": "0.18.14", - "@esbuild/linux-arm64": "0.18.14", - "@esbuild/linux-ia32": "0.18.14", - "@esbuild/linux-loong64": "0.18.14", - "@esbuild/linux-mips64el": "0.18.14", - "@esbuild/linux-ppc64": "0.18.14", - "@esbuild/linux-riscv64": "0.18.14", - "@esbuild/linux-s390x": "0.18.14", - "@esbuild/linux-x64": "0.18.14", - "@esbuild/netbsd-x64": "0.18.14", - "@esbuild/openbsd-x64": "0.18.14", - "@esbuild/sunos-x64": "0.18.14", - "@esbuild/win32-arm64": "0.18.14", - "@esbuild/win32-ia32": "0.18.14", - "@esbuild/win32-x64": "0.18.14" - } - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2533,7 +2267,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, "engines": { "node": ">=10" }, @@ -2638,6 +2371,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/got": { + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -2717,6 +2474,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -2745,6 +2507,18 @@ "npm": ">=1.3.7" } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -2762,7 +2536,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, "engines": { "node": ">=10.17.0" } @@ -2817,6 +2590,14 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2858,9 +2639,9 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", "dependencies": { "has": "^1.0.3" }, @@ -2928,11 +2709,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==" - }, "node_modules/is-ssh": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", @@ -2945,7 +2721,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -2979,8 +2754,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "node_modules/isobject": { "version": "3.0.1", @@ -2992,9 +2766,9 @@ } }, "node_modules/isomorphic-ws": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", - "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", "peerDependencies": { "ws": "*" } @@ -3034,9 +2808,9 @@ } }, "node_modules/jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.10.0.tgz", + "integrity": "sha512-KEhB/eLGLomWGPTb+/RNbYsTjIyx03JmbqAyIyiXBuNSa7CmNrJd5ysFhblayzs/e/vbOPMUaLnjHUMhGp4yLw==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -3057,6 +2831,11 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -3084,24 +2863,12 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonpath-plus": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", - "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz", + "integrity": "sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg==", "engines": { - "node": ">=12.0.0" + "node": ">=6.0" } }, "node_modules/jsprim": { @@ -3128,6 +2895,14 @@ "resolved": "https://registry.npmjs.org/just-zip-it/-/just-zip-it-2.3.1.tgz", "integrity": "sha512-h8Y3DAVTZRP3Weq7btWYfkYHQGhxiuKzfOO7Ec+x8XaDcBvbOsC2jIdezC6tEzbt+A4fTJTREnj3gF5DyMkFfw==" }, + "node_modules/keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -3156,11 +2931,6 @@ "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", "dev": true }, - "node_modules/lite-deep-equal": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/lite-deep-equal/-/lite-deep-equal-1.0.6.tgz", - "integrity": "sha512-hQAVr+zFQWBz9eoER1NfrCEUx6GszLKeRtThjPVyM3HrsdtT9uTuBhnJwReHAtDOutZJyPr4qoVaqlcFjbV7uA==" - }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -3220,6 +2990,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3231,11 +3009,15 @@ "node": ">=10" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -3282,11 +3064,18 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3458,16 +3247,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-releases": { "version": "1.1.75", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", @@ -3483,11 +3262,21 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, "dependencies": { "path-key": "^3.0.0" }, @@ -3507,16 +3296,14 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "optional": true, "engines": { "node": ">= 6" } }, "node_modules/oidc-token-hash": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", - "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", - "optional": true, + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz", + "integrity": "sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==", "engines": { "node": "^10.13.0 || >=12.0.0" } @@ -3533,7 +3320,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -3545,15 +3331,34 @@ } }, "node_modules/openid-client": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.2.tgz", - "integrity": "sha512-lIhsdPvJ2RneBm3nGBBhQchpe3Uka//xf7WPHTIglery8gnckvW7Bd9IaQzekzXJvWthCMyi/xVEyGW0RFPytw==", - "optional": true, + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.7.5.tgz", + "integrity": "sha512-9APA9gHikzzRCc9z3lmIsZ1LcRHho9uTXxt567QlVmAmS2qoVpChTOdla7US9RrbiZsIh50xXd9DpLzh68FtgQ==", "dependencies": { - "jose": "^4.14.1", + "aggregate-error": "^3.1.0", + "got": "^11.8.0", + "jose": "^2.0.5", "lru-cache": "^6.0.0", - "object-hash": "^2.2.0", - "oidc-token-hash": "^5.0.3" + "make-error": "^1.3.6", + "object-hash": "^2.0.1", + "oidc-token-hash": "^5.0.1" + }, + "engines": { + "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/jose": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", + "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" }, "funding": { "url": "https://github.com/sponsors/panva" @@ -3573,7 +3378,15 @@ "word-wrap": "^1.2.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" } }, "node_modules/p-limit": { @@ -3714,7 +3527,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -3743,12 +3555,6 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -3825,52 +3631,6 @@ "node": ">=8" } }, - "node_modules/postcss": { - "version": "8.4.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz", - "integrity": "sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3921,12 +3681,6 @@ "node": ">=0.6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3947,6 +3701,17 @@ } ] }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4057,18 +3822,6 @@ "node": ">= 0.12" } }, - "node_modules/request/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/request/node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -4087,28 +3840,23 @@ "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -4139,6 +3887,14 @@ "node": ">=4" } }, + "node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4168,22 +3924,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rollup": { - "version": "3.26.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz", - "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4250,9 +3990,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -4309,7 +4049,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4321,7 +4060,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -4375,8 +4113,7 @@ "node_modules/signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "node_modules/slash": { "version": "3.0.0", @@ -4410,15 +4147,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -4502,20 +4230,10 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, "engines": { "node": ">=6" } @@ -4544,17 +4262,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tapable": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", @@ -4679,18 +4386,15 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "psl": "^1.1.28", + "punycode": "^2.1.1" }, "engines": { - "node": ">=6" + "node": ">=0.8" } }, "node_modules/traverse": { @@ -4702,14 +4406,6 @@ "node": "*" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/ts-loader": { "version": "9.2.8", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.8.tgz", @@ -4729,34 +4425,6 @@ "webpack": "^5.0.0" } }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", - "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -4831,18 +4499,9 @@ } }, "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" }, "node_modules/unzipper": { "version": "0.10.11", @@ -4896,28 +4555,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/utf-8-validate": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", - "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4951,61 +4588,6 @@ "extsprintf": "^1.2.0" } }, - "node_modules/vite": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.4.tgz", - "integrity": "sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==", - "dev": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.25", - "rollup": "^3.25.2" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "node_modules/vscode-kubernetes-tools-api": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/vscode-kubernetes-tools-api/-/vscode-kubernetes-tools-api-1.3.0.tgz", @@ -5014,11 +4596,6 @@ "vscode": "^1.31.0" } }, - "node_modules/vscode-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", - "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -5159,11 +4736,19 @@ "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/acorn-import-assertions": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", + "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -5181,9 +4766,9 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5218,15 +4803,15 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -5317,164 +4902,10 @@ }, "dependencies": { "@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", - "dev": true - }, - "@esbuild/android-arm": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.14.tgz", - "integrity": "sha512-blODaaL+lngG5bdK/t4qZcQvq2BBqrABmYwqPPcS5VRxrCSGHb9R/rA3fqxh7R18I7WU4KKv+NYkt22FDfalcg==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.14.tgz", - "integrity": "sha512-rZ2v+Luba5/3D6l8kofWgTnqE+qsC/L5MleKIKFyllHTKHrNBMqeRCnZI1BtRx8B24xMYxeU32iIddRQqMsOsg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.14.tgz", - "integrity": "sha512-qSwh8y38QKl+1Iqg+YhvCVYlSk3dVLk9N88VO71U4FUjtiSFylMWK3Ugr8GC6eTkkP4Tc83dVppt2n8vIdlSGg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.14.tgz", - "integrity": "sha512-9Hl2D2PBeDYZiNbnRKRWuxwHa9v5ssWBBjisXFkVcSP5cZqzZRFBUWEQuqBHO4+PKx4q4wgHoWtfQ1S7rUqJ2Q==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.14.tgz", - "integrity": "sha512-ZnI3Dg4ElQ6tlv82qLc/UNHtFsgZSKZ7KjsUNAo1BF1SoYDjkGKHJyCrYyWjFecmXpvvG/KJ9A/oe0H12odPLQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.14.tgz", - "integrity": "sha512-h3OqR80Da4oQCIa37zl8tU5MwHQ7qgPV0oVScPfKJK21fSRZEhLE4IIVpmcOxfAVmqjU6NDxcxhYaM8aDIGRLw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.14.tgz", - "integrity": "sha512-ha4BX+S6CZG4BoH9tOZTrFIYC1DH13UTCRHzFc3GWX74nz3h/N6MPF3tuR3XlsNjMFUazGgm35MPW5tHkn2lzQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.14.tgz", - "integrity": "sha512-5+7vehI1iqru5WRtJyU2XvTOvTGURw3OZxe3YTdE9muNNIdmKAVmSHpB3Vw2LazJk2ifEdIMt/wTWnVe5V98Kg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.14.tgz", - "integrity": "sha512-IXORRe22In7U65NZCzjwAUc03nn8SDIzWCnfzJ6t/8AvGx5zBkcLfknI+0P+hhuftufJBmIXxdSTbzWc8X/V4w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.14.tgz", - "integrity": "sha512-BfHlMa0nibwpjG+VXbOoqJDmFde4UK2gnW351SQ2Zd4t1N3zNdmUEqRkw/srC1Sa1DRBE88Dbwg4JgWCbNz/FQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.14.tgz", - "integrity": "sha512-j2/Ex++DRUWIAaUDprXd3JevzGtZ4/d7VKz+AYDoHZ3HjJzCyYBub9CU1wwIXN+viOP0b4VR3RhGClsvyt/xSw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.14.tgz", - "integrity": "sha512-qn2+nc+ZCrJmiicoAnJXJJkZWt8Nwswgu1crY7N+PBR8ChBHh89XRxj38UU6Dkthl2yCVO9jWuafZ24muzDC/A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.14.tgz", - "integrity": "sha512-aGzXzd+djqeEC5IRkDKt3kWzvXoXC6K6GyYKxd+wsFJ2VQYnOWE954qV2tvy5/aaNrmgPTb52cSCHFE+Z7Z0yg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.14.tgz", - "integrity": "sha512-8C6vWbfr0ygbAiMFLS6OPz0BHvApkT2gCboOGV76YrYw+sD/MQJzyITNsjZWDXJwPu9tjrFQOVG7zijRzBCnLw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.14.tgz", - "integrity": "sha512-G/Lf9iu8sRMM60OVGOh94ZW2nIStksEcITkXdkD09/T6QFD/o+g0+9WVyR/jajIb3A0LvBJ670tBnGe1GgXMgw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.14.tgz", - "integrity": "sha512-TBgStYBQaa3EGhgqIDM+ECnkreb0wkcKqL7H6m+XPcGUoU4dO7dqewfbm0mWEQYH3kzFHrzjOFNpSAVzDZRSJw==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.14.tgz", - "integrity": "sha512-stvCcjyCQR2lMTroqNhAbvROqRjxPEq0oQ380YdXxA81TaRJEucH/PzJ/qsEtsHgXlWFW6Ryr/X15vxQiyRXVg==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.14.tgz", - "integrity": "sha512-apAOJF14CIsN5ht1PA57PboEMsNV70j3FUdxLmA2liZ20gEQnfTG5QU0FhENo5nwbTqCB2O3WDsXAihfODjHYw==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.14.tgz", - "integrity": "sha512-fYRaaS8mDgZcGybPn2MQbn1ZNZx+UXFSUoS5Hd2oEnlsyUcr/l3c6RnXf1bLDRKKdLRSabTmyCy7VLQ7VhGdOQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.14.tgz", - "integrity": "sha512-1c44RcxKEJPrVj62XdmYhxXaU/V7auELCmnD+Ri+UCt+AGxTvzxl9uauQhrFso8gj6ZV1DaORV0sT9XSHOAk8Q==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.14.tgz", - "integrity": "sha512-EXAFttrdAxZkFQmpvcAQ2bywlWUsONp/9c2lcfvPUhu8vXBBenCXpoq9YkUvVP639ld3YGiYx0YUQ6/VQz3Maw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.14.tgz", - "integrity": "sha512-K0QjGbcskx+gY+qp3v4/940qg8JitpXbdxFhRDA1aYoNaPff88+aEwoq45aqJ+ogpxQxmU0ZTjgnrQD/w8iiUg==", - "dev": true, - "optional": true + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", + "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", + "dev": true }, "@eslint/eslintrc": { "version": "1.2.1", @@ -5560,80 +4991,57 @@ } }, "@kubernetes/client-node": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.18.1.tgz", - "integrity": "sha512-F3JiK9iZnbh81O/da1tD0h8fQMi/MDttWc/JydyUVnjPEom55wVfnpl4zQ/sWD4uKB8FlxYRPiLwV2ZXB+xPKw==", + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.16.3.tgz", + "integrity": "sha512-L7IckuyuPfhd+/Urib8MRas9D6sfKEq8IaITYcaE6LlU+Y8MeD7MTbuW6Yb2WdeRuFN8HPSS47mxPnOUNYBXEg==", "requires": { "@types/js-yaml": "^4.0.1", - "@types/node": "^18.11.17", + "@types/node": "^10.12.0", "@types/request": "^2.47.1", - "@types/ws": "^8.5.3", + "@types/stream-buffers": "^3.0.3", + "@types/tar": "^4.0.3", + "@types/underscore": "^1.8.9", + "@types/ws": "^6.0.1", "byline": "^5.0.0", - "isomorphic-ws": "^5.0.0", + "execa": "5.0.0", + "isomorphic-ws": "^4.0.1", "js-yaml": "^4.1.0", - "jsonpath-plus": "^7.2.0", - "openid-client": "^5.3.0", + "jsonpath-plus": "^0.19.0", + "openid-client": "^4.1.1", "request": "^2.88.0", "rfc4648": "^1.3.0", + "shelljs": "^0.8.5", "stream-buffers": "^3.0.2", "tar": "^6.1.11", "tmp-promise": "^3.0.2", - "tslib": "^2.4.1", - "underscore": "^1.13.6", - "ws": "^8.11.0" + "tslib": "^1.9.3", + "underscore": "^1.9.1", + "ws": "^7.3.1" }, "dependencies": { "@types/node": { - "version": "18.16.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz", - "integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==" + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" }, - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "execa": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } } } }, - "@microsoft/1ds-core-js": { - "version": "3.2.13", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.13.tgz", - "integrity": "sha512-CluYTRWcEk0ObG5EWFNWhs87e2qchJUn0p2D21ZUa3PWojPZfPSBs4//WIE0MYV8Qg1Hdif2ZTwlM7TbYUjfAg==", - "requires": { - "@microsoft/applicationinsights-core-js": "2.8.15", - "@microsoft/applicationinsights-shims": "^2.0.2", - "@microsoft/dynamicproto-js": "^1.1.7" - } - }, - "@microsoft/1ds-post-js": { - "version": "3.2.13", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.13.tgz", - "integrity": "sha512-HgS574fdD19Bo2vPguyznL4eDw7Pcm1cVNpvbvBLWiW3x4e1FCQ3VMXChWnAxCae8Hb0XqlA2sz332ZobBavTA==", - "requires": { - "@microsoft/1ds-core-js": "3.2.13", - "@microsoft/applicationinsights-shims": "^2.0.2", - "@microsoft/dynamicproto-js": "^1.1.7" - } - }, - "@microsoft/applicationinsights-core-js": { - "version": "2.8.15", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.15.tgz", - "integrity": "sha512-yYAs9MyjGr2YijQdUSN9mVgT1ijI1FPMgcffpaPmYbHAVbQmF7bXudrBWHxmLzJlwl5rfep+Zgjli2e67lwUqQ==", - "requires": { - "@microsoft/applicationinsights-shims": "2.0.2", - "@microsoft/dynamicproto-js": "^1.1.9" - } - }, - "@microsoft/applicationinsights-shims": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz", - "integrity": "sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==" - }, - "@microsoft/dynamicproto-js": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", - "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" - }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5660,12 +5068,41 @@ "fastq": "^1.6.0" } }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, + "@sindresorhus/is": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", @@ -5713,11 +5150,10 @@ "@types/node": "*" } }, - "@types/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-y1JGY9nExw7elDF/PUGqV4wQi7PVqa4hsxHf2fXLpc0jpD5kzx5BRldqeuqJQAuctnbsIqWxzYoGxSayvgynBQ==", - "dev": true + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, "@types/js-yaml": { "version": "4.0.3", @@ -5730,12 +5166,28 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "requires": { + "@types/node": "*" + } + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", "dev": true }, + "@types/minipass": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-3.1.0.tgz", + "integrity": "sha512-b2yPKwCrB8x9SB65kcCistMoe3wrYnxxt5rJSZ1kprw0uOXvhuKi9kTQ746Y+Pbqoh+9C0N4zt0ztmTnG9yg7A==", + "requires": { + "@types/node": "*" + } + }, "@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", @@ -5758,6 +5210,14 @@ "form-data": "^2.5.0" } }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", @@ -5774,11 +5234,33 @@ "@types/node": "*" } }, + "@types/stream-buffers": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.4.tgz", + "integrity": "sha512-qU/K1tb2yUdhXkLIATzsIPwbtX6BpZk0l3dPW6xqWyhfzzM1ECaQ/8faEnu3CNraLiQ9LHyQQPBGp7N9Fbs25w==", + "requires": { + "@types/node": "*" + } + }, + "@types/tar": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.5.tgz", + "integrity": "sha512-cgwPhNEabHaZcYIy5xeMtux2EmYBitfqEceBUi2t5+ETy4dW6kswt6WX4+HqLeiiKOo42EXbGiDmVJ2x+vi37Q==", + "requires": { + "@types/minipass": "*", + "@types/node": "*" + } + }, "@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" }, + "@types/underscore": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.3.tgz", + "integrity": "sha512-Fl1TX1dapfXyDqFg2ic9M+vlXRktcPJrc4PR7sRc7sdVrjavg/JHlbUXBt8qWWqhJrmSqg3RNAkAPRiOYw6Ahw==" + }, "@types/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", @@ -5804,9 +5286,9 @@ "dev": true }, "@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", + "integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==", "requires": { "@types/node": "*" } @@ -5922,13 +5404,9 @@ "dev": true }, "@vscode/extension-telemetry": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz", - "integrity": "sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w==", - "requires": { - "@microsoft/1ds-core-js": "^3.2.3", - "@microsoft/1ds-post-js": "^3.2.3" - } + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.4.7.tgz", + "integrity": "sha512-tXjChgxFN6EAfa6LIy/PE3fIbIvj0BnELUmGAG+8tUpsLY3g2iACcVM/UJJXtsU6db29tMqCLITUX2Dd3N06GA==" }, "@vscode/test-electron": { "version": "2.1.3", @@ -6129,13 +5607,6 @@ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, - "acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "requires": {} - }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6152,6 +5623,15 @@ "debug": "4" } }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -6346,19 +5826,40 @@ "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", "dev": true }, - "bufferutil": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", - "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", - "requires": { - "node-gyp-build": "^4.3.0" - } - }, "byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + } + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6487,6 +5988,11 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -6509,6 +6015,14 @@ "shallow-clone": "^3.0.0" } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6575,7 +6089,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6604,12 +6117,32 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6715,36 +6248,6 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, - "esbuild": { - "version": "0.18.14", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.14.tgz", - "integrity": "sha512-uNPj5oHPYmj+ZhSQeYQVFZ+hAlJZbAGOmmILWIqrGvPVlNLbyOvU5Bu6Woi8G8nskcx0vwY0iFoMPrzT86Ko+w==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.18.14", - "@esbuild/android-arm64": "0.18.14", - "@esbuild/android-x64": "0.18.14", - "@esbuild/darwin-arm64": "0.18.14", - "@esbuild/darwin-x64": "0.18.14", - "@esbuild/freebsd-arm64": "0.18.14", - "@esbuild/freebsd-x64": "0.18.14", - "@esbuild/linux-arm": "0.18.14", - "@esbuild/linux-arm64": "0.18.14", - "@esbuild/linux-ia32": "0.18.14", - "@esbuild/linux-loong64": "0.18.14", - "@esbuild/linux-mips64el": "0.18.14", - "@esbuild/linux-ppc64": "0.18.14", - "@esbuild/linux-riscv64": "0.18.14", - "@esbuild/linux-s390x": "0.18.14", - "@esbuild/linux-x64": "0.18.14", - "@esbuild/netbsd-x64": "0.18.14", - "@esbuild/openbsd-x64": "0.18.14", - "@esbuild/sunos-x64": "0.18.14", - "@esbuild/win32-arm64": "0.18.14", - "@esbuild/win32-ia32": "0.18.14", - "@esbuild/win32-x64": "0.18.14" - } - }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -7151,8 +6654,7 @@ "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" }, "getpass": { "version": "0.1.7", @@ -7230,6 +6732,24 @@ "slash": "^3.0.0" } }, + "got": { + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, "graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -7292,6 +6812,11 @@ } } }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -7313,6 +6838,15 @@ "sshpk": "^1.7.0" } }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -7326,8 +6860,7 @@ "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" }, "ignore": { "version": "5.2.0", @@ -7361,6 +6894,11 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7396,9 +6934,9 @@ } }, "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", "requires": { "has": "^1.0.3" } @@ -7445,11 +6983,6 @@ "isobject": "^3.0.1" } }, - "is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==" - }, "is-ssh": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", @@ -7461,8 +6994,7 @@ "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "is-typedarray": { "version": "1.0.0", @@ -7484,8 +7016,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -7494,9 +7025,9 @@ "dev": true }, "isomorphic-ws": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", - "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", "requires": {} }, "isstream": { @@ -7527,9 +7058,9 @@ } }, "jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.10.0.tgz", + "integrity": "sha512-KEhB/eLGLomWGPTb+/RNbYsTjIyx03JmbqAyIyiXBuNSa7CmNrJd5ysFhblayzs/e/vbOPMUaLnjHUMhGp4yLw==" }, "js-yaml": { "version": "4.1.0", @@ -7544,6 +7075,11 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -7571,16 +7107,10 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, "jsonpath-plus": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", - "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==" + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz", + "integrity": "sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg==" }, "jsprim": { "version": "1.4.2", @@ -7603,6 +7133,14 @@ "resolved": "https://registry.npmjs.org/just-zip-it/-/just-zip-it-2.3.1.tgz", "integrity": "sha512-h8Y3DAVTZRP3Weq7btWYfkYHQGhxiuKzfOO7Ec+x8XaDcBvbOsC2jIdezC6tEzbt+A4fTJTREnj3gF5DyMkFfw==" }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -7625,11 +7163,6 @@ "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", "dev": true }, - "lite-deep-equal": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/lite-deep-equal/-/lite-deep-equal-1.0.6.tgz", - "integrity": "sha512-hQAVr+zFQWBz9eoER1NfrCEUx6GszLKeRtThjPVyM3HrsdtT9uTuBhnJwReHAtDOutZJyPr4qoVaqlcFjbV7uA==" - }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -7676,6 +7209,11 @@ } } }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7684,11 +7222,15 @@ "yallist": "^4.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", @@ -7722,8 +7264,12 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "minimatch": { "version": "3.1.2", @@ -7862,11 +7408,6 @@ } } }, - "node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==" - }, "node-releases": { "version": "1.1.75", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", @@ -7879,11 +7420,15 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, "requires": { "path-key": "^3.0.0" } @@ -7896,14 +7441,12 @@ "object-hash": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "optional": true + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" }, "oidc-token-hash": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", - "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", - "optional": true + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz", + "integrity": "sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==" }, "once": { "version": "1.4.0", @@ -7917,21 +7460,32 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } }, "openid-client": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.2.tgz", - "integrity": "sha512-lIhsdPvJ2RneBm3nGBBhQchpe3Uka//xf7WPHTIglery8gnckvW7Bd9IaQzekzXJvWthCMyi/xVEyGW0RFPytw==", - "optional": true, + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.7.5.tgz", + "integrity": "sha512-9APA9gHikzzRCc9z3lmIsZ1LcRHho9uTXxt567QlVmAmS2qoVpChTOdla7US9RrbiZsIh50xXd9DpLzh68FtgQ==", "requires": { - "jose": "^4.14.1", + "aggregate-error": "^3.1.0", + "got": "^11.8.0", + "jose": "^2.0.5", "lru-cache": "^6.0.0", - "object-hash": "^2.2.0", - "oidc-token-hash": "^5.0.3" + "make-error": "^1.3.6", + "object-hash": "^2.0.1", + "oidc-token-hash": "^5.0.1" + }, + "dependencies": { + "jose": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", + "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + } } }, "optionator": { @@ -7948,6 +7502,11 @@ "word-wrap": "^1.2.3" } }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8069,8 +7628,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", @@ -8093,12 +7651,6 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -8153,25 +7705,6 @@ } } }, - "postcss": { - "version": "8.4.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz", - "integrity": "sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==", - "dev": true, - "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "dependencies": { - "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true - } - } - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8213,18 +7746,17 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8318,15 +7850,6 @@ "mime-types": "^2.1.12" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -8340,22 +7863,20 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -8379,6 +7900,14 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -8398,15 +7927,6 @@ "glob": "^7.1.3" } }, - "rollup": { - "version": "3.26.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz", - "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8438,9 +7958,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "requires": { "lru-cache": "^6.0.0" } @@ -8490,7 +8010,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -8498,8 +8017,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "shell-escape-tag": { "version": "2.0.2", @@ -8540,8 +8058,7 @@ "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "slash": { "version": "3.0.0", @@ -8571,12 +8088,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -8645,17 +8156,10 @@ "ansi-regex": "^5.0.1" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, "strip-json-comments": { "version": "3.1.1", @@ -8672,11 +8176,6 @@ "has-flag": "^4.0.0" } }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, "tapable": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", @@ -8766,15 +8265,12 @@ } }, "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "traverse": { @@ -8783,11 +8279,6 @@ "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", "dev": true }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" - }, "ts-loader": { "version": "9.2.8", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.8.tgz", @@ -8800,28 +8291,6 @@ "semver": "^7.3.4" } }, - "tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "requires": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tsconfig-paths-webpack-plugin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", - "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.1.2" - } - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -8871,15 +8340,9 @@ "dev": true }, "underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" }, "unzipper": { "version": "0.10.11", @@ -8937,24 +8400,6 @@ "punycode": "^2.1.0" } }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "utf-8-validate": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", - "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", - "requires": { - "node-gyp-build": "^4.3.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8982,28 +8427,11 @@ "extsprintf": "^1.2.0" } }, - "vite": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.4.tgz", - "integrity": "sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==", - "dev": true, - "requires": { - "esbuild": "^0.18.10", - "fsevents": "~2.3.2", - "postcss": "^8.4.25", - "rollup": "^3.25.2" - } - }, "vscode-kubernetes-tools-api": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/vscode-kubernetes-tools-api/-/vscode-kubernetes-tools-api-1.3.0.tgz", "integrity": "sha512-5+4OdwrRinoTsE8i6s8pPY+BbGh5U7DNAA/3pn5wlOhvQpivaBMdi89bRKfdAffPjt/bvHodje4BFm5GSNjFxQ==" }, - "vscode-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", - "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" - }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -9044,6 +8472,15 @@ "terser-webpack-plugin": "^5.1.3", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" + }, + "dependencies": { + "acorn-import-assertions": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", + "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", + "dev": true, + "requires": {} + } } }, "webpack-cli": { @@ -9100,7 +8537,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -9112,9 +8548,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "workerpool": { @@ -9140,9 +8576,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", "requires": {} }, "y18n": { diff --git a/package.json b/package.json index 6d9204ac..02b652c5 100644 --- a/package.json +++ b/package.json @@ -2,20 +2,19 @@ "name": "vscode-gitops-tools", "displayName": "GitOps Tools for Flux", "description": "GitOps automation tools for continuous delivery of Kubernetes and Cloud Native applications", - "version": "0.26.0", + "version": "0.24.3", "author": "Kingdon Barrett ", "contributors": [ "Kingdon Barrett ", "Juozas Gaigalas ", "Alexander ", "Taras Novak ", - "Leonardo Murillo ", - "Seth Falco " + "Leonardo Murillo " ], "publisher": "weaveworks", "icon": "resources/icons/gitops-logo.png", "engines": { - "vscode": "^1.82.0", + "vscode": "^1.63.0", "npm": ">=7.0.0" }, "categories": [ @@ -98,16 +97,6 @@ "title": "Resume", "category": "GitOps" }, - { - "command": "gitops.manualPromotion", - "title": "Disable Automatic Promotion", - "category": "GitOps" - }, - { - "command": "gitops.autoPromotion", - "title": "Enable Automatic Promotion", - "category": "GitOps" - }, { "command": "gitops.flux.checkPrerequisites", "title": "Flux Check Prerequisites", @@ -133,11 +122,6 @@ "title": "Reconcile GitRepository for Path", "category": "GitOps" }, - { - "command": "gitops.flux.reconcileWorkloadWithSource", - "title": "Reconcile with Source", - "category": "GitOps" - }, { "command": "gitops.flux.reconcileWorkload", "title": "Reconcile", @@ -167,28 +151,14 @@ }, { "command": "gitops.views.createGitRepository", - "title": "Create Source from Path", + "title": "Create GitRepository from Path", "category": "GitOps" }, { "command": "gitops.createKustomization", "title": "Create Kustomization from Path", "icon": "$(add)", - "category": "GitOps" - }, - { - "command": "gitops.kubectlApplyPath", - "title": "Apply (kubectl apply -f)", - "category": "GitOps" - }, - { - "command": "gitops.kubectlDeletePath", - "title": "Delete (kubectl delete -f)", - "category": "GitOps" - }, - { - "command": "gitops.kubectlApplyKustomization", - "title": "Apply Kustomization Directory (kubectl apply -k)", + "enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled", "category": "GitOps" }, { @@ -196,28 +166,14 @@ "title": "Add Source", "category": "GitOps", "icon": "$(add)", - "enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled" + "enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled" }, { "command": "gitops.addKustomization", "title": "Add Kustomization", "category": "GitOps", "icon": "$(add)", - "enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled" - }, - { - "command": "gitops.views.expandAllSources", - "title": "Expand All", - "category": "GitOps", - "icon": "$(expand-all)", - "enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled" - }, - { - "command": "gitops.views.expandAllWorkloads", - "title": "Expand All", - "category": "GitOps", - "icon": "$(expand-all)", - "enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled" + "enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled" }, { "command": "gitops.views.deleteWorkload", @@ -242,12 +198,7 @@ }, { "command": "gitops.editor.openResource", - "title": "Open Resource", - "category": "GitOps" - }, - { - "command": "gitops.editor.openKubeconfig", - "title": "Open Kubeconfig", + "title": "View Config", "category": "GitOps" }, { @@ -269,16 +220,6 @@ "command": "gitops.views.createFromTemplate", "title": "Create from Template", "category": "GitOps" - }, - { - "command": "gitops.views.openInWgePortal", - "title": "Open in Weave Gitops Enterprise...", - "category": "GitOps" - }, - { - "command": "gitops.views.setContextToGitopsCluster", - "title": "Set as Current Context", - "category": "GitOps" } ], "viewsContainers": { @@ -305,8 +246,8 @@ "name": "Workloads" }, { - "id": "gitops.views.wge", - "name": "Weave GitOps", + "id": "gitops.views.templates", + "name": "Templates", "when": "config.gitops.weaveGitopsEnterprise" }, { @@ -318,54 +259,72 @@ "configuration": { "title": "GitOps Tools", "properties": { - "gitops.doFluxCheck": { - "type": "boolean", - "default": true, - "description": "Enable Flux Check (uncheck to skip flux check)" - }, - "gitops.suppressDebugMessages": { - "type": "boolean", - "default": false, - "description": "Do not emit debug-level messages (only error, info, warn)" - }, "gitops.weaveGitopsEnterprise": { "type": "boolean", "default": false, - "description": "Enable WGE features" - }, - "gitops.kubectlRequestTimeout": { - "type": "string", - "default": "10s", - "description": "kubectl --request-timeout" - }, - "gitops.execTimeout": { - "type": "string", - "default": "60", - "description": "Seconds until SIGTERM for every shell exec (except `kubectl proxy`). Set to 0 for no timeout." - }, - "gitops.ignoreConfigRecommendations": { - "type": "boolean", - "default": false, - "description": "Stop recommending changes to editor or extension settings." + "description": "Enable WGE GitOpsTemplates feature" } } }, "viewsWelcome": [ + { + "view": "gitops.views.clusters", + "contents": "Loading Clusters ...", + "when": "gitops:loadingClusters" + }, + { + "view": "gitops.views.clusters", + "contents": "No clusters.", + "when": "!gitops:loadingClusters && gitops:noClusters" + }, + { + "view": "gitops.views.clusters", + "contents": "Failed to load cluster contexts.", + "when": "!gitops:loadingClusters && gitops:failedToLoadClusterContexts" + }, { "view": "gitops.views.sources", "contents": "[Enable GitOps](command:gitops.flux.install) for the selected Cluster to view Sources.", - "when": "gitops:currentClusterGitOpsNotEnabled && !gitops:clusterUnreachable" + "when": "gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected" + }, + { + "view": "gitops.views.sources", + "contents": "Loading Sources ...", + "when": "gitops:loadingSources && !gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled" + }, + { + "view": "gitops.views.sources", + "contents": "No sources.", + "when": "!gitops:loadingSources && !gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected && gitops:noSources" + }, + { + "view": "gitops.views.sources", + "contents": "Select GitOps Cluster to view Sources.", + "when": "gitops:noClusterSelected" }, { "view": "gitops.views.workloads", "contents": "[Enable GitOps](command:gitops.flux.install) for the selected Cluster to view Workloads.", - "when": "gitops:currentClusterGitOpsNotEnabled && !gitops:clusterUnreachable" - } - ], - "submenus": [ + "when": "gitops:currentClusterGitOpsNotEnabled" + }, { - "id": "gitops.explorer", - "label": "GitOps" + "view": "gitops.views.workloads", + "contents": "Loading Workloads ...", + "when": "gitops:loadingWorkloads && !gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled" + }, + { + "view": "gitops.views.workloads", + "contents": "No workloads.", + "when": "!gitops:loadingWorkloads && !gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected && gitops:noWorkloads" + }, + { + "view": "gitops.views.workloads", + "contents": "Select GitOps Cluster to view Workloads.", + "when": "gitops:noClusterSelected" + }, + { + "view": "gitops.views.documentation", + "contents": "Loading Topics ..." } ], "menus": { @@ -380,11 +339,6 @@ "group": "1", "when": "view == gitops.views.clusters" }, - { - "command": "gitops.editor.openKubeconfig", - "group": "1", - "when": "view == gitops.views.clusters" - }, { "command": "gitops.addSource", "group": "navigation@0", @@ -408,20 +362,10 @@ { "command": "gitops.views.refreshResourcesTreeView", "group": "navigation@1", - "when": "view == gitops.views.wge" + "when": "view == gitops.views.templates" }, { "command": "gitops.views.showWorkloadsHelpMessage", - "group": "navigation@1", - "when": "view == gitops.views.workloads" - }, - { - "command": "gitops.views.expandAllSources", - "group": "navigation@2", - "when": "view == gitops.views.sources" - }, - { - "command": "gitops.views.expandAllWorkloads", "group": "navigation@2", "when": "view == gitops.views.workloads" } @@ -454,67 +398,52 @@ }, { "command": "gitops.flux.reconcileSource", - "when": "viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/", - "group": "navigation@0" - }, - { - "command": "gitops.flux.reconcileWorkloadWithSource", - "when": "viewItem =~ /(Kustomization;|HelmRelease;)/", + "when": "view == gitops.views.sources && viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/", "group": "navigation@0" }, { "command": "gitops.flux.reconcileWorkload", - "when": "viewItem =~ /(Kustomization;|HelmRelease;)/", - "group": "navigation@1" + "when": "view == gitops.views.workloads && viewItem =~ /(Kustomization;|HelmRelease;)/", + "group": "navigation@0" }, { "command": "gitops.suspend", - "when": "viewItem =~ /notSuspend;/", + "when": "view =~ /(gitops.views.sources|gitops.views.workloads)/ && viewItem =~ /(GitRepository;|OCIRepository;|Kustomization;|HelmRelease;|HelmRepository;)/ && viewItem =~ /notSuspend;/", "group": "navigation@1" }, { "command": "gitops.resume", - "when": "viewItem =~ /suspend;/", + "when": "view =~ /(gitops.views.sources|gitops.views.workloads)/ && viewItem =~ /(GitRepository;|OCIRepository;|Kustomization;|HelmRelease;|HelmRepository;)/ && viewItem =~ /suspend;/", "group": "navigation@1" }, - { - "command": "gitops.manualPromotion", - "when": "viewItem =~ /autoPromotion;/", - "group": "1" - }, - { - "command": "gitops.autoPromotion", - "when": "viewItem =~ /manualPromotion;/", - "group": "1" - }, { "command": "gitops.views.deleteWorkload", - "when": "viewItem =~ /(Kustomization;|HelmRelease;)/", + "when": "view == gitops.views.workloads && viewItem =~ /(Kustomization;|HelmRelease;)/", "group": "navigation@2" }, { "command": "gitops.views.deleteSource", - "when": "viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/", + "when": "view == gitops.views.sources && viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/", "group": "navigation@2" }, { "command": "gitops.views.pullGitRepository", - "when": "viewItem =~ /GitRepository;/", + "when": "view == gitops.views.sources && viewItem =~ /GitRepository;/", "group": "navigation@3" }, { "command": "gitops.addKustomization", - "when": "viewItem =~ /GitRepository;|OCIRepository;|Bucket;/", + "when": "view == gitops.views.sources && viewItem =~ /GitRepository;|OCIRepository;|Bucket;/", "group": "navigation@3" }, { "command": "gitops.editor.showLogs", - "when": "viewItem =~ /(Deployment;)/" + "when": "view =~ /^(gitops.views.clusters)$/ && viewItem =~ /(Deployment;)/" }, { "command": "gitops.copyResourceName", - "when": "view =~ /^(gitops.views.sources|gitops.views.workloads||gitops.views.wge)$/ && !(viewItem =~ /Container;/)", - "group": "9" + "when": "view =~ /^(gitops.views.sources|gitops.views.workloads)$/", + "group": "navigation@9" }, { "command": "gitops.flux.trace", @@ -524,50 +453,18 @@ { "command": "gitops.views.createFromTemplate", "group": "1", - "when": "viewItem =~ /GitOpsTemplate;/" - }, - { - "command": "gitops.views.openInWgePortal", - "group": "1", - "when": "viewItem =~ /hasWgePortal;/" - }, - { - "command": "gitops.views.setContextToGitopsCluster", - "group": "1", - "when": "viewItem =~ /GitopsCluster;/" + "when": "view == gitops.views.templates" } ], - "gitops.explorer": [ - { - "command": "gitops.kubectlApplyPath", - "group": "1" - }, - { - "command": "gitops.kubectlDeletePath", - "group": "1" - }, - { - "command": "gitops.kubectlApplyKustomization", - "group": "1", - "when": "explorerResourceIsFolder" - }, + "explorer/context": [ { - "command": "gitops.views.createGitRepository", - "group": "2" + "command": "gitops.views.createGitRepository" }, { - "command": "gitops.flux.reconcileRepository", - "when": "explorerResourceIsFolder", - "group": "2" + "command": "gitops.flux.reconcileRepository" }, { - "command": "gitops.createKustomization", - "group": "2" - } - ], - "explorer/context": [ - { - "submenu": "gitops.explorer" + "command": "gitops.createKustomization" } ], "commandPalette": [ @@ -583,14 +480,6 @@ "command": "gitops.resume", "when": "never" }, - { - "command": "gitops.manualPromotion", - "when": "never" - }, - { - "command": "gitops.autoPromotion", - "when": "never" - }, { "command": "gitops.flux.check", "when": "never" @@ -607,10 +496,6 @@ "command": "gitops.flux.reconcileSource", "when": "never" }, - { - "command": "gitops.flux.reconcileWorkloadWithSource", - "when": "never" - }, { "command": "gitops.flux.reconcileWorkload", "when": "never" @@ -662,10 +547,6 @@ { "command": "gitops.dev.showGlobalState", "when": "gitops:isDev" - }, - { - "command": "gitops.views.setContextToGitopsCluster", - "when": "never" } ] } @@ -675,7 +556,6 @@ ], "devDependencies": { "@types/git-url-parse": "^9.0.1", - "@types/is-running": "^2.1.0", "@types/mocha": "^9.1.0", "@types/node": "14.x", "@types/semver": "^7.3.9", @@ -690,40 +570,26 @@ "eslint": "^8.11.0", "glob": "^7.2.0", "mocha": "^9.2.2", - "tough-cookie": ">=4.1.3", "ts-loader": "^9.2.8", - "tsconfig-paths-webpack-plugin": "^4.0.1", "typescript": "^4.5.5", - "vite": ">=2.9.16", "webpack": "^5.70.0", - "webpack-cli": "^4.9.2", - "word-wrap": ">=1.2.4" + "webpack-cli": "^4.9.2" }, "dependencies": { - "@kubernetes/client-node": "^0.18.1", - "@types/ws": "^8.5.4", - "@vscode/extension-telemetry": "^0.6.0", - "bufferutil": "^4.0.7", + "@kubernetes/client-node": "^0.16.2", + "@vscode/extension-telemetry": "^0.4.7", "change-case": "^4.1.2", "extract-zip": "^2.0.1", "git-url-parse": "^13.0.0", - "is-running": "^2.1.0", "jose": ">=2.0.6", - "lite-deep-equal": "^1.0.6", "parse-path": ">=5.0.0", "parse-url": ">=8.1.0", - "semver": "^7.5.2", + "semver": "^7.3.5", "shell-escape-tag": "^2.0.2", "shelljs": "^0.8.5", "tinytim": "^0.1.1", - "tough-cookie": ">=4.1.3", - "tree-kill": "^1.2.2", - "utf-8-validate": "^6.0.3", "uuid": "^9.0.0", - "vite": ">=2.9.16", - "vscode-kubernetes-tools-api": "^1.3.0", - "vscode-uri": "^3.0.7", - "word-wrap": ">=1.2.4" + "vscode-kubernetes-tools-api": "^1.3.0" }, "activationEvents": [], "__metadata": { diff --git a/src/cli/azure/azurePrereqs.ts b/src/azure/azurePrereqs.ts similarity index 96% rename from src/cli/azure/azurePrereqs.ts rename to src/azure/azurePrereqs.ts index 87f36710..c3475b05 100644 --- a/src/cli/azure/azurePrereqs.ts +++ b/src/azure/azurePrereqs.ts @@ -1,7 +1,6 @@ import { window } from 'vscode'; - -import * as shell from 'cli/shell/exec'; -import { ClusterProvider } from 'types/kubernetes/clusterProvider'; +import { ClusterProvider } from '../kubernetes/types/kubernetesTypes'; +import { shell } from '../shell'; import { AzureClusterProvider } from './azureTools'; /** diff --git a/src/cli/azure/azureTools.ts b/src/azure/azureTools.ts similarity index 90% rename from src/cli/azure/azureTools.ts rename to src/azure/azureTools.ts index 5fc12d6f..bf357dd5 100644 --- a/src/cli/azure/azureTools.ts +++ b/src/azure/azureTools.ts @@ -1,17 +1,16 @@ -import { Uri, env, window } from 'vscode'; - -import { fluxTools } from 'cli/flux/fluxTools'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import * as shell from 'cli/shell/exec'; -import { ShellResult, shellCodeError } from 'cli/shell/exec'; -import { refreshAllTreeViewsCommand } from 'commands/refreshTreeViews'; -import { ClusterMetadata } from 'data/globalState'; -import { globalState, telemetry } from 'extension'; -import { ClusterProvider } from 'types/kubernetes/clusterProvider'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { parseJson } from 'utils/jsonUtils'; -import { checkAzurePrerequisites } from './azurePrereqs'; +import { window, env, Uri } from 'vscode'; +import { globalState, telemetry } from '../extension'; +import { ClusterMetadata } from '../globalState'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { ClusterProvider, ConfigMap, knownClusterProviders } from '../kubernetes/types/kubernetesTypes'; +import { shell, shellCodeError, ShellResult } from '../shell'; +import { TelemetryErrorEventNames } from '../telemetry'; +import { parseJson } from '../utils/jsonUtils'; +import { getCurrentClusterInfo, refreshAllTreeViews } from '../views/treeViews'; +import { failed } from '../errorable'; +import { fluxTools } from '../flux/fluxTools'; import { getAzureMetadata } from './getAzureMetadata'; +import { checkAzurePrerequisites } from './azurePrereqs'; export type AzureClusterProvider = ClusterProvider.AKS | ClusterProvider.AzureARC; @@ -120,7 +119,7 @@ class AzureTools { if(answer === 'Yes') { const result = await shell.execWithOutput(command); if (result?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_ENABLE_GITOPS); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_ENABLE_GITOPS); } } else if(answer === 'Use as "Generic" cluster') { await this.enableGitOpsGeneric(contextName); @@ -128,17 +127,17 @@ class AzureTools { } async enableGitOpsGeneric(contextName: string) { - const context = kubeConfig.getContextObject(contextName); - if (!context) { + const currentClusterInfo = await getCurrentClusterInfo(); + if (failed(currentClusterInfo)) { return; } - const clusterName = context.cluster; - const clusterMetadata: ClusterMetadata = globalState.getClusterMetadata(clusterName || contextName) || {}; + const clusterName = currentClusterInfo.result.clusterName; + const clusterMetadata: ClusterMetadata = globalState.getClusterMetadata(clusterName) || {}; clusterMetadata.clusterProvider = ClusterProvider.Generic; - globalState.setClusterMetadata(clusterName || contextName, clusterMetadata); - refreshAllTreeViewsCommand(); + globalState.setClusterMetadata(clusterName, clusterMetadata); + refreshAllTreeViews(); await fluxTools.install(contextName); } @@ -175,7 +174,7 @@ class AzureTools { clusterProvider, ); if (disableGitOpsShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_DISABLE_GITOPS); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_DISABLE_GITOPS); } } @@ -197,7 +196,7 @@ class AzureTools { ); if (configurationShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_LIST_FLUX_CONFIGURATIONS); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_LIST_FLUX_CONFIGURATIONS); return; } @@ -230,7 +229,7 @@ class AzureTools { ); if (createKustomizationShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_CREATE_WORKLOAD); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_CREATE_WORKLOAD); window.showErrorMessage(shellCodeError(createKustomizationShellResult)); return; } @@ -297,7 +296,7 @@ class AzureTools { ); if (createSourceShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_CREATE_SOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_CREATE_SOURCE); return; } @@ -400,7 +399,7 @@ class AzureTools { clusterProvider, ); if (deleteSourceShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_DELETE_SOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_DELETE_SOURCE); } } @@ -417,7 +416,7 @@ class AzureTools { clusterProvider, ); if (deleteSourceShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_DELETE_WORKLOAD); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_DELETE_WORKLOAD); } } @@ -440,7 +439,7 @@ class AzureTools { clusterProvider, ); if (suspendShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_SUSPEND_SOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_SUSPEND_SOURCE); } } @@ -463,7 +462,7 @@ class AzureTools { clusterProvider, ); if (resumeShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_AZ_RESUME_SOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_AZ_RESUME_SOURCE); } } diff --git a/src/cli/azure/getAzureMetadata.ts b/src/azure/getAzureMetadata.ts similarity index 85% rename from src/cli/azure/getAzureMetadata.ts rename to src/azure/getAzureMetadata.ts index f3bf1fc5..e90ffee4 100644 --- a/src/cli/azure/getAzureMetadata.ts +++ b/src/azure/getAzureMetadata.ts @@ -1,13 +1,10 @@ +import { window } from 'vscode'; import safesh from 'shell-escape-tag'; -import { QuickPickItem, window } from 'vscode'; - -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { invokeKubectlCommand } from 'cli/kubernetes/kubernetesToolsKubectl'; -import * as shell from 'cli/shell/exec'; -import { ShellResult } from 'cli/shell/exec'; -import { ClusterProvider } from 'types/kubernetes/clusterProvider'; -import { ConfigMap } from 'types/kubernetes/kubernetesTypes'; -import { parseJson } from 'utils/jsonUtils'; +import { QuickPickItem } from 'vscode'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { ClusterProvider, ConfigMap } from '../kubernetes/types/kubernetesTypes'; +import { shell, ShellResult } from '../shell'; +import { parseJson } from '../utils/jsonUtils'; import { AzureClusterProvider, AzureConstants } from './azureTools'; export interface AzureMetadata { @@ -58,8 +55,7 @@ export async function getAzureMetadata(contextName: string, clusterProvider: Azu return metadata; } - const context = kubeConfig.getContextObject(contextName)!; - const clusterName = context.cluster || contextName; + const clusterName = await kubernetesTools.getClusterName(contextName); return await getAzCliOrUserMetadata(clusterName); } @@ -166,9 +162,9 @@ export async function askUserForAzureMetadata( export async function getAzureConfigMapMetadata(contextName: string, clusterProvider: AzureClusterProvider): Promise { let configMapShellResult: ShellResult | undefined; if (clusterProvider === ClusterProvider.AKS) { - configMapShellResult = await invokeKubectlCommand(safesh`get configmaps extension-manager-config -n ${AzureConstants.KubeSystemNamespace} --context=${contextName} --ignore-not-found -o json`); + configMapShellResult = await kubernetesTools.invokeKubectlCommand(safesh`get configmaps extension-manager-config -n ${AzureConstants.KubeSystemNamespace} --context=${contextName} --ignore-not-found -o json`); } else { - configMapShellResult = await invokeKubectlCommand(safesh`get configmaps azure-clusterconfig -n ${AzureConstants.ArcNamespace} --context=${contextName} --ignore-not-found -o json`); + configMapShellResult = await kubernetesTools.invokeKubectlCommand(safesh`get configmaps azure-clusterconfig -n ${AzureConstants.ArcNamespace} --context=${contextName} --ignore-not-found -o json`); } // the configmap does not exist, or something else went wrong diff --git a/src/cli/kubernetes/apiResources.ts b/src/cli/kubernetes/apiResources.ts deleted file mode 100644 index d5a0e6da..00000000 --- a/src/cli/kubernetes/apiResources.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { redrawResourcesTreeViews, refreshResourcesTreeViews } from 'commands/refreshTreeViews'; -import { currentContextData, loadContextData } from 'data/contextData'; -import { setVSCodeContext, telemetry } from 'extension'; -import { ContextId } from 'types/extensionIds'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { clusterDataProvider } from 'ui/treeviews/treeViews'; -import { restartKubeProxy } from './kubectlProxy'; -import { invokeKubectlCommand } from './kubernetesToolsKubectl'; - -export enum ApiState { - Loading, - Loaded, - ClusterUnreachable, -} - -export type KindApiParams = { - plural: string; // configmaps, deployments, gitrepositories, ... - group: string; // '', apps, source.toolkit.fluxcd.io, ... - version: string; // v1, v1beta2, ... -}; - - - -/** - * Return all available kubernetes resource kinds - */ -export function getAvailableResourcePlurals(): string[] | undefined { - const context = currentContextData(); - const plurals: string[] = []; - if(context.apiResources) { - context.apiResources.forEach((value, key) => { - plurals.push(value.plural); - }); - - return plurals; - } -} - - -export function getAPIParams(kind: Kind): KindApiParams | undefined { - const context = currentContextData(); - - if(context.apiResources) { - return context.apiResources.get(kind); - } -} - - -export async function loadAvailableResourceKinds() { - const context = currentContextData(); - context.apiResources = undefined; - context.apiState = ApiState.Loading; - // will set their content to Loading API... - redrawResourcesTreeViews(); - - const kindsShellResult = await invokeKubectlCommand('api-resources --verbs=list -o wide'); - if (kindsShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_AVAILABLE_RESOURCE_KINDS); - console.warn(`Failed to get resource kinds: ${kindsShellResult?.stderr}`); - context.apiState = ApiState.ClusterUnreachable; - setVSCodeContext(ContextId.ClusterUnreachable, true); - clusterDataProvider.updateCurrentContextChildNodes(); - refreshResourcesTreeViews(); - redrawResourcesTreeViews(); - return; - } - - const lines = kindsShellResult.stdout - .split('\n') - .filter(line => line.length).slice(1); - - context.apiResources = new Map(); - - lines.map(line => { - let cols = line.split(/\s+/); - - - if(cols.length === 7) { - // delete optional SHORTNAMES column - cols = cols.slice(0, 1).concat(cols.slice(2)); - } - const kind = cols[3] as Kind; - const plural = cols[0]; - const groupVersion = cols[1]; - let [group, version] = groupVersion.split('/'); - if(!version) { - version = group; - group = ''; - } - context.apiResources?.set(kind, { plural, group, version }); - }); - - console.log('apiResources loaded'); - - context.apiState = ApiState.Loaded; - setVSCodeContext(ContextId.ClusterUnreachable, false); - clusterDataProvider.updateCurrentContextChildNodes(); - - await restartKubeProxy(); - - // give proxy init callbacks time to fire - setTimeout(() => { - refreshResourcesTreeViews(); - loadContextData(); - }, 100); -} diff --git a/src/cli/kubernetes/clusterProvider.ts b/src/cli/kubernetes/clusterProvider.ts deleted file mode 100644 index 1f151e4a..00000000 --- a/src/cli/kubernetes/clusterProvider.ts +++ /dev/null @@ -1,104 +0,0 @@ -import safesh from 'shell-escape-tag'; - -import { AzureConstants } from 'cli/azure/azureTools'; -import { globalState, telemetry } from 'extension'; -import { ClusterProvider } from 'types/kubernetes/clusterProvider'; -import { ConfigMap, Node } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { parseJson, parseJsonItems } from 'utils/jsonUtils'; -import { notAnErrorServerNotRunning } from './kubectlGet'; -import { kubeConfig } from './kubernetesConfig'; -import { invokeKubectlCommand } from './kubernetesToolsKubectl'; - -/** - * Try to detect known cluster providers. Returns user selected cluster type if that is set. - * @param context target context to get resources from. - * TODO: maybe use Errorable? - */ -export async function detectClusterProvider(contextName: string): Promise { - const context = kubeConfig.getContextObject(contextName)!; - const clusterName = context.cluster || contextName; - const clusterMetadata = globalState.getClusterMetadata(clusterName); - - if(clusterMetadata?.clusterProvider) { - return clusterMetadata.clusterProvider; - } - - const tryProviderAzureARC = await isClusterAzureARC(contextName); - if (tryProviderAzureARC === ClusterProvider.AzureARC) { - return ClusterProvider.AzureARC; - } else if (tryProviderAzureARC === ClusterProvider.DetectionFailed) { - return ClusterProvider.DetectionFailed; - } - - const tryProviderAKS = await isClusterAKS(contextName); - if (tryProviderAKS === ClusterProvider.AKS) { - return ClusterProvider.AKS; - } else if (tryProviderAKS === ClusterProvider.DetectionFailed) { - return ClusterProvider.DetectionFailed; - } - - return ClusterProvider.Generic; -} - -/** - * Try to determine if the cluster is AKS or not. - * @param context target context to get resources from. - */ -async function isClusterAKS(context: string): Promise { - const nodesShellResult = await invokeKubectlCommand(safesh`get nodes --context=${context} -o json`); - - if (nodesShellResult?.code !== 0) { - console.warn(`Failed to get nodes from "${context}" context to determine the cluster type. ${nodesShellResult?.stderr}`); - if (nodesShellResult?.stderr && !notAnErrorServerNotRunning.test(nodesShellResult.stderr)) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_NODES_TO_DETECT_AKS_CLUSTER); - } - return ClusterProvider.DetectionFailed; - } - - const nodes: Node[] = parseJsonItems(nodesShellResult.stdout); - - const firstNode = nodes[0]; - - if (!firstNode) { - console.warn(`No nodes in the "${context}" context to determine the cluster type.`); - return ClusterProvider.DetectionFailed; - } - - const providerID = firstNode.spec?.providerID; - - if (providerID?.startsWith('azure:///')) { - return ClusterProvider.AKS; - } else { - return ClusterProvider.Generic; - } -} - -/** - * Try to determine if the cluster is managed by Azure ARC or not. - * @param context target context to get resources from. - */ -async function isClusterAzureARC(context: string): Promise { - const configmapShellResult = await invokeKubectlCommand(safesh`get configmaps azure-clusterconfig -n ${AzureConstants.ArcNamespace} --context=${context} --ignore-not-found -o json`); - - if (configmapShellResult?.code !== 0) { - if (configmapShellResult?.stderr && !notAnErrorServerNotRunning.test(configmapShellResult.stderr)) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_CONFIGMAPS_TO_DETECT_ARC_CLUSTER); - } - console.warn(`Failed to get configmaps from "${context}" context to determine the cluster type. ${configmapShellResult?.stderr}`); - return ClusterProvider.DetectionFailed; - } - - const stdout = configmapShellResult.stdout; - if (stdout.length) { - const azureClusterconfigConfigMap: ConfigMap | undefined = parseJson(stdout); - if (azureClusterconfigConfigMap === undefined) { - return ClusterProvider.DetectionFailed; - } else { - return ClusterProvider.AzureARC; - } - } - - return ClusterProvider.Generic; -} - diff --git a/src/cli/kubernetes/kubectlGet.ts b/src/cli/kubernetes/kubectlGet.ts deleted file mode 100644 index f352fa1d..00000000 --- a/src/cli/kubernetes/kubectlGet.ts +++ /dev/null @@ -1,223 +0,0 @@ -import safesh from 'shell-escape-tag'; - -import { telemetry } from 'extension'; -import { k8sGet, k8sList } from 'k8s/list'; -import { Bucket } from 'types/flux/bucket'; -import { Canary } from 'types/flux/canary'; -import { GitOpsTemplate } from 'types/flux/gitOpsTemplate'; -import { GitRepository } from 'types/flux/gitRepository'; -import { GitOpsSet } from 'types/flux/gitopsset'; -import { HelmRelease } from 'types/flux/helmRelease'; -import { HelmRepository } from 'types/flux/helmRepository'; -import { Kustomization } from 'types/flux/kustomization'; -import { OCIRepository } from 'types/flux/ociRepository'; -import { Pipeline } from 'types/flux/pipeline'; -import { Deployment, Kind, KubernetesObject, Pod, qualifyToolkitKind } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { parseJson, parseJsonItems } from 'utils/jsonUtils'; -import { window } from 'vscode'; -import { getAvailableResourcePlurals } from './apiResources'; -import { invokeKubectlCommand } from './kubernetesToolsKubectl'; -/** - * RegExp for the Error that should not be sent in telemetry. - * Server doesn't have a resource type = when GitOps not enabled - * No connection could be made... = when cluster not running - */ -export const notAnErrorServerDoesntHaveResourceTypeRegExp = /the server doesn't have a resource type/i; -export const notAnErrorServerNotRunning = /no connection could be made because the target machine actively refused it/i; - -/** - * Get one resource object by kind/name and namespace - * @param name name of the target resource - * @param namespace namespace of the target resource - * @param kind kind of the target resource - */ -export async function getResource(name: string, namespace: string, kind: Kind): Promise { - const item = await k8sGet(name, namespace, kind); - if(item) { - return item as T; - } - - let fqKind = qualifyToolkitKind(kind); - - const shellResult = await invokeKubectlCommand(`get ${fqKind}/${name} --namespace=${namespace} -o json`); - if (shellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_RESOURCE); - return; - } - - return parseJson(shellResult.stdout) as T; -} - -export async function getResourcesAllNamespaces(kind: Kind, telemetryError: TelemetryError): Promise { - const list = await k8sList(kind); - if(list !== undefined) { - return list as T[]; - } - - let fqKind = qualifyToolkitKind(kind); - - const shellResult = await invokeKubectlCommand(`get ${fqKind} -A -o json`); - if (shellResult?.code !== 0) { - console.warn(`Failed to \`kubectl get ${kind} -A\`: ${shellResult?.stderr}`); - if (shellResult?.stderr && !notAnErrorServerDoesntHaveResourceTypeRegExp.test(shellResult.stderr)) { - telemetry.sendError(telemetryError); - } - return []; - } - - const items = parseJsonItems(shellResult.stdout); - return items as T[]; -} - - -export async function getBuckets(): Promise { - return getResourcesAllNamespaces(Kind.Bucket, TelemetryError.FAILED_TO_GET_BUCKETS); -} - -export async function getGitRepositories(): Promise { - return getResourcesAllNamespaces(Kind.GitRepository, TelemetryError.FAILED_TO_GET_GIT_REPOSITORIES); -} - -export async function getHelmRepositories(): Promise { - return getResourcesAllNamespaces(Kind.HelmRepository, TelemetryError.FAILED_TO_GET_HELM_REPOSITORIES); -} - -export async function getOciRepositories(): Promise { - return getResourcesAllNamespaces(Kind.OCIRepository, TelemetryError.FAILED_TO_GET_OCI_REPOSITORIES); -} - -export async function getKustomizations(): Promise { - return getResourcesAllNamespaces(Kind.Kustomization, TelemetryError.FAILED_TO_GET_KUSTOMIZATIONS); -} - -export async function getHelmReleases(): Promise { - return getResourcesAllNamespaces(Kind.HelmRelease, TelemetryError.FAILED_TO_GET_HELM_RELEASES); -} - -export async function getGitOpsTemplates(): Promise { - return getResourcesAllNamespaces(Kind.GitOpsTemplate, TelemetryError.FAILED_TO_GET_GITOPSTEMPLATES); -} - -export async function getCanaries(): Promise { - return getResourcesAllNamespaces(Kind.Canary, TelemetryError.FAILED_TO_GET_HELM_RELEASES); -} - -export async function getPipelines(): Promise { - return getResourcesAllNamespaces(Kind.Pipeline, TelemetryError.FAILED_TO_GET_HELM_RELEASES); -} - -export async function getGitOpsSet(): Promise { - return getResourcesAllNamespaces(Kind.GitOpsSet, TelemetryError.FAILED_TO_GET_HELM_RELEASES); -} - - -/** - * Get all flux system deployments. - */ -export async function getFluxControllers(context?: string): Promise { - const contextArg = context ? safesh`--context ${context}` : ''; - - const fluxDeploymentShellResult = await invokeKubectlCommand(`get deployment --namespace=flux-system ${contextArg} -o json`); - - if (fluxDeploymentShellResult?.code !== 0) { - console.warn(`Failed to get flux controllers: ${fluxDeploymentShellResult?.stderr}`); - - if (fluxDeploymentShellResult?.stderr && !notAnErrorServerNotRunning.test(fluxDeploymentShellResult.stderr)) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_FLUX_CONTROLLERS); - } - return []; - } - - - return parseJsonItems(fluxDeploymentShellResult.stdout); -} - - - -/** - * Return all kubernetes resources that were created by a kustomize/helmRelease. - * @param name name of the kustomize/helmRelease object - * @param namespace namespace of the kustomize/helmRelease object - */ -export async function getHelmReleaseChildren( - name: string, - namespace: string, -): Promise { - // return []; - const resourceKinds = getAvailableResourcePlurals(); - if (!resourceKinds) { - return; - } - - const labelNameSelector = `-l helm.toolkit.fluxcd.io/name=${name}`; - const labelNamespaceSelector = `-l helm.toolkit.fluxcd.io/namespace=${namespace}`; - - const query = `get ${resourceKinds.join(',')} ${labelNameSelector} ${labelNamespaceSelector} -A -o json`; - const shellResult = await invokeKubectlCommand(query); - - if (!shellResult || shellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_CHILDREN_OF_A_WORKLOAD); - window.showErrorMessage(`Failed to get HelmRelease created resources: ${shellResult?.stderr}`); - return; - } - - return parseJsonItems(shellResult.stdout); -} - - -export async function getCanaryChildren( - name: string, -): Promise { - // return []; - const resourceKinds = getAvailableResourcePlurals(); - if (!resourceKinds) { - return []; - } - - const labelNameSelector = `-l app=${name}`; - - const query = `get ${resourceKinds.join(',')} ${labelNameSelector} -A -o json`; - const shellResult = await invokeKubectlCommand(query); - - if (!shellResult || shellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_CHILDREN_OF_A_WORKLOAD); - window.showErrorMessage(`Failed to get HelmRelease created resources: ${shellResult?.stderr}`); - return []; - } - - return parseJsonItems(shellResult.stdout); -} - - -/** - * Get pods by a deployment name. - * @param name pod target name - * @param namespace pod target namespace - */ -export async function getPodsOfADeployment(name = '', namespace = ''): Promise { - let nameArg: string; - - if (name === 'fluxconfig-agent' || name === 'fluxconfig-controller') { - nameArg = name ? `-l app.kubernetes.io/component=${name}` : ''; - } else { - nameArg = name ? `-l app=${name}` : ''; - } - - let namespaceArg = ''; - if (namespace === 'all') { - namespaceArg = '--all-namespaces'; - } else if (namespace.length > 0) { - namespaceArg = `--namespace=${namespace}`; - } - - const podResult = await invokeKubectlCommand(`get pod ${nameArg} ${namespaceArg} -o json`); - - if (podResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_PODS_OF_A_DEPLOYMENT); - console.warn(`Failed to get pods: ${podResult?.stderr}`); - return []; - } - - return parseJsonItems(podResult?.stdout); -} diff --git a/src/cli/kubernetes/kubectlGetNamespace.ts b/src/cli/kubernetes/kubectlGetNamespace.ts deleted file mode 100644 index 7e29cde6..00000000 --- a/src/cli/kubernetes/kubectlGetNamespace.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { window } from 'vscode'; -import { telemetry } from 'extension'; -import { k8sListNamespaces } from 'k8s/list'; -import { Namespace } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { parseJsonItems } from 'utils/jsonUtils'; -import { invokeKubectlCommand } from './kubernetesToolsKubectl'; - -/** - * Get namespaces from current context. - */ -let nsCache: Namespace[] = []; -export async function getNamespaces(): Promise { - const k8sns = await k8sListNamespaces(); - if (k8sns !== undefined) { - nsCache = k8sns; - return k8sns; - } - - const shellResult = await invokeKubectlCommand('get ns -o json'); - - if (shellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_NAMESPACES); - window.showErrorMessage(`Failed to get namespaces ${shellResult?.stderr}`); - return []; - } - - nsCache = parseJsonItems(shellResult.stdout); - return nsCache; -} - -export function getCachedNamespaces(): Namespace[] { - return nsCache; -} - -export async function getNamespace(name: string): Promise { - const cachedNs = getCachedNamespaces().find(ns => ns.metadata.name === name); - if (cachedNs) { - return cachedNs; - } - - const namespaces = await getNamespaces(); - return namespaces.find(ns => ns.metadata.name === name); -} diff --git a/src/cli/kubernetes/kubectlProxy.ts b/src/cli/kubernetes/kubectlProxy.ts deleted file mode 100644 index b5ced2fb..00000000 --- a/src/cli/kubernetes/kubectlProxy.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { KubeConfig } from '@kubernetes/client-node'; -import { ChildProcess } from 'child_process'; -import * as shell from 'cli/shell/exec'; -import { isActive } from 'extension'; -import { createK8sClients, destroyK8sClients } from 'k8s/client'; -import { createProxyConfig } from 'k8s/createKubeProxyConfig'; - -export let proxyProc: ChildProcess | undefined; -export let kubeProxyConfig: KubeConfig | undefined; - -// tries to keep alive the `kubectl proxy` process -// if process dies or errors out it will be stopped -// and restarted by the 2000ms interval -// if context changes proxy is stopped and restarted immediately -export function kubeProxyKeepAlive() { - // keep alive - setInterval(async () => { - if(!proxyProc && isActive) { - destroyK8sClients(); - await startKubeProxy(); - } - }, 2000); -} - -async function startKubeProxy() { - if(proxyProc) { - await stopKubeProxy(); - } - - proxyProc = shell.execProc('kubectl proxy -p 0'); - - procListen(proxyProc); -} - -function procListen(p: ChildProcess) { - p.on('exit', async code => { - if(proxyProc?.pid === p.pid) { - stopKubeProxy(); - } - }); - - p.on('error', err => { - p.kill(); - }); - - p.stdout?.on('data', (data: string) => { - if(data.includes('Starting to serve on')) { - const port = parseInt(data.split(':')[1].trim()); - kubeProxyConfig = createProxyConfig(port); - createK8sClients(); - } - }); - - p.stderr?.on('data', (data: string) => { - console.warn(`kubectl proxy ${p.pid} STDERR: ${data}`); - p.kill(); - }); -} - - -export async function stopKubeProxy() { - if(proxyProc) { - if(!proxyProc.killed) { - proxyProc.kill(); - } - proxyProc = undefined; - kubeProxyConfig = undefined; - - destroyK8sClients(); - } -} - -export async function restartKubeProxy() { - if(proxyProc) { - await stopKubeProxy(); - } - await startKubeProxy(); -} diff --git a/src/cli/kubernetes/kubernetesConfig.ts b/src/cli/kubernetes/kubernetesConfig.ts deleted file mode 100644 index 38f9db5d..00000000 --- a/src/cli/kubernetes/kubernetesConfig.ts +++ /dev/null @@ -1,113 +0,0 @@ -import safesh from 'shell-escape-tag'; -import { window } from 'vscode'; - -import * as k8s from '@kubernetes/client-node'; -import { ActionOnInvalid } from '@kubernetes/client-node/dist/config_types'; -import { shellCodeError } from 'cli/shell/exec'; -import { setVSCodeContext, telemetry } from 'extension'; -import { ContextId } from 'types/extensionIds'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { reloadClustersTreeView } from 'ui/treeviews/treeViews'; -import { kcContextsListChanged, kcCurrentContextChanged, kcTextChanged } from 'utils/kubeConfigCompare'; -import { loadAvailableResourceKinds as loadApiResources } from './apiResources'; -import { loadKubeConfigPath } from './kubernetesConfigWatcher'; -import { invokeKubectlCommand } from './kubernetesToolsKubectl'; - -export enum KubeConfigState { - /* effectively KubeConfigState.Loading has meaning obnly at the extension init - * because subsequent kubeconfig updates are swapped-in atomically. but we keep track of it anyway - */ - Loading, - Loaded, - Failed, - NoContextSelected, -} - -export let kubeConfigState: KubeConfigState = KubeConfigState.Loading; - -export const kubeConfig: k8s.KubeConfig = new k8s.KubeConfig(); - -// reload the kubeconfig via kubernetes-tools. fire events if things have changed -export async function syncKubeConfig(forceReloadResourceKinds = false) { - - kubeConfigState = KubeConfigState.Loading; - const configShellResult = await invokeKubectlCommand('config view'); - - if (configShellResult?.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_GET_KUBECTL_CONFIG); - const path = await loadKubeConfigPath(); - window.showErrorMessage(`Failed to load kubeconfig: ${path} ${shellCodeError(configShellResult)}`); - kubeConfigState = KubeConfigState.Failed; - return; - } - - const newKubeConfig = new k8s.KubeConfig(); - newKubeConfig.loadFromString(configShellResult.stdout, {onInvalidEntry: ActionOnInvalid.FILTER}); - - kubeConfigState = KubeConfigState.Loaded; - - if (kcTextChanged(kubeConfig, newKubeConfig)) { - await kubeconfigChanged(newKubeConfig, forceReloadResourceKinds); - } else if(forceReloadResourceKinds) { - loadApiResources(); - } -} - - - -async function kubeconfigChanged(newKubeConfig: k8s.KubeConfig, forceReloadResourceKinds: boolean) { - const contextsListChanged = kcContextsListChanged(kubeConfig, newKubeConfig); - const contextChanged = kcCurrentContextChanged(kubeConfig, newKubeConfig); - - // load the changed kubeconfig globally so that the following code use the new config - kubeConfig.loadFromString(newKubeConfig.exportConfig(), {onInvalidEntry: ActionOnInvalid.FILTER}); - - if(!currentContextExists()) { - kubeConfigState = KubeConfigState.NoContextSelected; - } - - - if (contextChanged) { - setVSCodeContext(ContextId.CurrentClusterGitOpsNotEnabled, false); - setVSCodeContext(ContextId.ClusterUnreachable, false); - } - - if (contextChanged || forceReloadResourceKinds) { - reloadClustersTreeView(); - loadApiResources(); - } else if (contextsListChanged) { - reloadClustersTreeView(); - } -} - -/** - * Sets current kubectl context. - * @param contextName Kubectl context name to use. - * @returns `undefined` in case of an error or Object with information about - * whether or not context was switched or didn't need it (current). - */ -export async function setCurrentContext(contextName: string): Promise { - if (kubeConfig.getCurrentContext() === contextName) { - return { - isChanged: false, - }; - } - - const setContextShellResult = await invokeKubectlCommand(safesh`config use-context ${contextName}`); - if (setContextShellResult?.stderr) { - telemetry.sendError(TelemetryError.FAILED_TO_SET_CURRENT_KUBERNETES_CONTEXT); - window.showErrorMessage(`Failed to set kubectl context to ${contextName}: ${setContextShellResult?.stderr}`); - return; - } - - return { - isChanged: true, - }; -} - - -function currentContextExists() { - const name = kubeConfig.currentContext; - return !!kubeConfig.getContexts().find(context => context.name === name); -} - diff --git a/src/cli/kubernetes/kubernetesConfigWatcher.ts b/src/cli/kubernetes/kubernetesConfigWatcher.ts deleted file mode 100644 index 423bfdb1..00000000 --- a/src/cli/kubernetes/kubernetesConfigWatcher.ts +++ /dev/null @@ -1,85 +0,0 @@ -import * as vscode from 'vscode'; -import * as kubernetes from 'vscode-kubernetes-tools-api'; -import { ConfigurationV1_1 as KubernetesToolsConfigurationV1_1 } from 'vscode-kubernetes-tools-api/js/configuration/v1_1'; -import { Utils } from 'vscode-uri'; - -import { syncKubeConfig } from './kubernetesConfig'; - - -let fsWacher: vscode.FileSystemWatcher | undefined; -export let kubeConfigPath: string | undefined; - -export async function loadKubeConfigPath(): Promise { - const configuration = await kubernetes.extension.configuration.v1_1; - if (!configuration.available) { - return; - } - - return hostPath(configuration.api.getKubeconfigPath()); -} - -function hostPath(kcPath: KubernetesToolsConfigurationV1_1.KubeconfigPath): string | undefined { - if(kcPath.pathType === 'host') { - return kcPath.hostPath; - } -} - -export async function initKubeConfigWatcher() { - const configuration = await kubernetes.extension.configuration.v1_1; - if (!configuration.available) { - return; - } - - kubeConfigPath = await loadKubeConfigPath(); - - await initKubeConfigPathWatcher(); - - configuration.api.onDidChangeContext(context => { - syncKubeConfig(); - }); - - restartFsWatcher(); -} - - -async function initKubeConfigPathWatcher() { - const configuration = await kubernetes.extension.configuration.v1_1; - if (!configuration.available) { - return; - } - - configuration.api.onDidChangeKubeconfigPath(async kcpath => { - const path = hostPath(kcpath); - // fires twice - if(path !== kubeConfigPath) { - kubeConfigPath = path; - syncKubeConfig(); - - restartFsWatcher(); - } - }); -} - -function restartFsWatcher() { - stopFsWatcher(); - - if(!kubeConfigPath) { - return; - } - - const uri = vscode.Uri.file(kubeConfigPath); - const dirname = Utils.dirname(uri); - const basename = Utils.basename(uri); - - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(dirname, basename)); - watcher.onDidChange(e => { - syncKubeConfig(); - }); -} - -function stopFsWatcher() { - if(fsWacher) { - fsWacher.dispose(); - fsWacher = undefined; - } -} diff --git a/src/cli/kubernetes/kubernetesToolsKubectl.ts b/src/cli/kubernetes/kubernetesToolsKubectl.ts deleted file mode 100644 index 0a79d32a..00000000 --- a/src/cli/kubernetes/kubernetesToolsKubectl.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { window, workspace } from 'vscode'; -import * as kubernetes from 'vscode-kubernetes-tools-api'; - -import * as shell from 'cli/shell/exec'; -import { output } from 'cli/shell/output'; -import { telemetry } from 'extension'; -import { KubernetesObject, qualifyToolkitKind } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryError } from 'types/telemetryEventNames'; - -/** - * Defines Kubernetes Tools class for integration - * with Microsoft Kubernetes Tools extension API. - * @see https://github.com/Azure/vscode-kubernetes-tools - * @see https://github.com/Azure/vscode-kubernetes-tools-api - */ - -export let kubectlApi: kubernetes.KubectlV1 | undefined; - -/** - * Gets kubernetes tools extension kubectl api reference. - * @see https://github.com/Azure/vscode-kubernetes-tools-api - */ -async function getKubectlApi() { - if (kubectlApi) { - return kubectlApi; - } - - const kubectl = await kubernetes.extension.kubectl.v1; - if (!kubectl.available) { - window.showErrorMessage(`Kubernetes Tools Kubectl API is unavailable: ${kubectl.reason}`); - telemetry.sendError(TelemetryError.KUBERNETES_TOOLS_API_UNAVAILABLE, new Error(kubectl.reason)); - return; - } - kubectlApi = kubectl.api; - return kubectlApi; -} - -/** - * Invokes kubectl command via Kubernetes Tools API. - * @param command Kubectl command to run. - * @returns Kubectl command results. - */ -export async function invokeKubectlCommand(command: string, printOutput = true): Promise { - const kubectl = await getKubectlApi(); - if (!kubectl) { - return; - } - - let kubectlShellResult; - const commandWithArgs = `kubectl ${command} --request-timeout ${getRequestTimeout()}`; - kubectlShellResult = await shell.exec(commandWithArgs); - - if(printOutput) { - output.send(`> kubectl ${command}`, { - channelName: 'GitOps: kubectl', - newline: 'single', - revealOutputView: false, - }); - - if (kubectlShellResult?.code === 0) { - output.send(kubectlShellResult.stdout, { - channelName: 'GitOps: kubectl', - revealOutputView: false, - }); - } else { - output.send(kubectlShellResult?.stderr || '', { - channelName: 'GitOps: kubectl', - revealOutputView: false, - logLevel: 'error', - }); - } - } - - return kubectlShellResult; -} - -export async function kubectlPatchNamespacedResource(resource: KubernetesObject, patch: string) { - const namespace = resource.metadata.namespace; - if(!namespace) { - return; - } - - const name = resource.metadata.name; - const kind = qualifyToolkitKind(resource.kind); - - const cmd = `kubectl patch ${kind} ${name} -n ${namespace} -p '${patch}' --type=merge`; - return shell.execWithOutput(cmd); -} - - -function getRequestTimeout(): string { - return workspace.getConfiguration('gitops').get('kubectlRequestTimeout') || '20s'; -} - diff --git a/src/commands.ts b/src/commands.ts new file mode 100644 index 00000000..15ccdfdc --- /dev/null +++ b/src/commands.ts @@ -0,0 +1,181 @@ +import { commands, Disposable, ExtensionContext, Uri, window } from 'vscode'; +import { copyResourceName } from './commands/copyResourceName'; +import { createGitRepositoryForPath } from './commands/createGitRepositoryForPath'; +import { createKustomizationForPath } from './commands/createKustomizationForPath'; +import { addSource } from './commands/addSource'; +import { addKustomization } from './commands/addKustomization'; +import { deleteWorkload } from './commands/deleteWorkload'; +import { deleteSource } from './commands/deleteSource'; +import { fluxDisableGitOps, fluxEnableGitOps } from './commands/enableDisableGitOps'; +import { fluxCheck } from './commands/fluxCheck'; +import { checkFluxPrerequisites } from './commands/fluxCheckPrerequisites'; +import { fluxReconcileRepositoryForPath } from './commands/fluxReconcileGitRepositoryForPath'; +import { fluxReconcileSourceCommand } from './commands/fluxReconcileSource'; +import { fluxReconcileWorkload } from './commands/fluxReconcileWorkload'; +import { installFluxCli } from './commands/installFluxCli'; +import { openResource } from './commands/openResource'; +import { pullGitRepository } from './commands/pullGitRepository'; +import { resume } from './commands/resume'; +import { setClusterProvider } from './commands/setClusterProvider'; +import { setCurrentKubernetesContext } from './commands/setCurrentKubernetesContext'; +import { showGlobalState } from './commands/showGlobalState'; +import { showInstalledVersions } from './commands/showInstalledVersions'; +import { showNewUserGuide } from './commands/showNewUserGuide'; +import { showLogs } from './commands/showLogs'; +import { showWorkloadsHelpMessage } from './commands/showWorkloadsHelpMessage'; +import { suspend } from './commands/suspend'; +import { trace } from './commands/trace'; +import { telemetry } from './extension'; +import { showOutputChannel } from './output'; +import { TelemetryErrorEventNames } from './telemetry'; +import { refreshAllTreeViews, refreshResourcesTreeViews } from './views/treeViews'; +import { createFromTemplate } from './commands/createFromTemplate'; + +/** + * Command ids registered by this extension + * or default vscode commands. + */ +export const enum CommandId { + + // vscode commands + /** + * Opens the provided resource in the editor. Can be a text or binary file, or an http(s) URL. + */ + VSCodeOpen = 'vscode.open', + VSCodeReload = 'workbench.action.reloadWindow', + /** + * Set vscode context to use in keybindings/menus/welcome views + * @see https://code.visualstudio.com/api/references/when-clause-contexts + */ + VSCodeSetContext = 'setContext', + + // kubectl + SetCurrentKubernetesContext = 'gitops.kubectl.setCurrentContext', + + // flux + Suspend = 'gitops.suspend', + Resume = 'gitops.resume', + FluxCheck = 'gitops.flux.check', + FluxCheckPrerequisites = 'gitops.flux.checkPrerequisites', + FluxEnableGitOps = 'gitops.flux.install', + FluxDisableGitOps = 'gitops.flux.uninstall', + FluxReconcileSource = 'gitops.flux.reconcileSource', + FluxReconcileRepository = 'gitops.flux.reconcileRepository', + FluxReconcileWorkload = 'gitops.flux.reconcileWorkload', + FluxTrace = 'gitops.flux.trace', + + // tree view + SetClusterProvider = 'gitops.setClusterProvider', + RefreshAllTreeViews = 'gitops.views.refreshAllTreeViews', + RefreshResourcesTreeView = 'gitops.views.refreshResourcesTreeView', + PullGitRepository = 'gitops.views.pullGitRepository', + CreateGitRepository = 'gitops.views.createGitRepository', + CreateKustomization = 'gitops.createKustomization', + ShowWorkloadsHelpMessage = 'gitops.views.showWorkloadsHelpMessage', + DeleteWorkload = 'gitops.views.deleteWorkload', + DeleteSource = 'gitops.views.deleteSource', + CopyResourceName = 'gitops.copyResourceName', + AddSource = 'gitops.addSource', + AddKustomization = 'gitops.addKustomization', + + // editor + EditorOpenResource = 'gitops.editor.openResource', + + // webview + ShowLogs = 'gitops.editor.showLogs', + ShowNewUserGuide = 'gitops.views.showNewUserGuide', + + // output commands + ShowOutputChannel = 'gitops.output.show', + + // others + ShowInstalledVersions = 'gitops.showInstalledVersions', + InstallFluxCli = 'gitops.installFluxCli', + ShowGlobalState = 'gitops.dev.showGlobalState', + CreateFromTemplate = 'gitops.views.createFromTemplate', +} + +let _context: ExtensionContext; + +/** + * Registers GitOps extension commands. + * @param context VSCode extension context. + */ +export function registerCommands(context: ExtensionContext) { + _context = context; + + // kubectl + registerCommand(CommandId.SetCurrentKubernetesContext, setCurrentKubernetesContext); + + // flux + registerCommand(CommandId.Suspend, suspend); + registerCommand(CommandId.Resume, resume); + registerCommand(CommandId.CreateKustomization, createKustomizationForPath); + registerCommand(CommandId.FluxCheck, fluxCheck); + registerCommand(CommandId.FluxCheckPrerequisites, checkFluxPrerequisites); + registerCommand(CommandId.FluxReconcileSource, fluxReconcileSourceCommand); + registerCommand(CommandId.FluxReconcileRepository, fluxReconcileRepositoryForPath); + registerCommand(CommandId.FluxReconcileWorkload, fluxReconcileWorkload); + registerCommand(CommandId.FluxEnableGitOps, fluxEnableGitOps); + registerCommand(CommandId.FluxDisableGitOps, fluxDisableGitOps); + registerCommand(CommandId.FluxTrace, trace); + + // tree views + registerCommand(CommandId.SetClusterProvider, setClusterProvider); + registerCommand(CommandId.RefreshAllTreeViews, refreshAllTreeViews); + registerCommand(CommandId.RefreshResourcesTreeView, refreshResourcesTreeViews); + registerCommand(CommandId.PullGitRepository, pullGitRepository); + registerCommand(CommandId.CreateGitRepository, (fileExplorerUri?: Uri) => { + // only pass one argument when running from File Explorer context menu + createGitRepositoryForPath(fileExplorerUri); + }); + registerCommand(CommandId.ShowWorkloadsHelpMessage, showWorkloadsHelpMessage); + registerCommand(CommandId.DeleteWorkload, deleteWorkload); + registerCommand(CommandId.DeleteSource, deleteSource); + registerCommand(CommandId.CopyResourceName, copyResourceName); + registerCommand(CommandId.AddSource, addSource); + registerCommand(CommandId.AddKustomization, addKustomization); + + + // editor + registerCommand(CommandId.EditorOpenResource, openResource); + + // webview + registerCommand(CommandId.ShowLogs, showLogs); + registerCommand(CommandId.ShowNewUserGuide, showNewUserGuide); + + // output + registerCommand(CommandId.ShowOutputChannel, showOutputChannel); + + // others + registerCommand(CommandId.ShowInstalledVersions, showInstalledVersions); + registerCommand(CommandId.InstallFluxCli, installFluxCli); + registerCommand(CommandId.ShowGlobalState, showGlobalState); + registerCommand(CommandId.CreateFromTemplate, createFromTemplate); +} + +/** + * Registers vscode extension command. + * @param commandId Command identifier. + * @param callback Command handler. + * @param thisArg The `this` context used when invoking the handler function. + */ +function registerCommand(commandId: string, callback: (...args: any[])=> any, thisArg?: any): void { + + const command: Disposable = commands.registerCommand(commandId, async(...args) => { + + // Show error in console when it happens in any of the commands registered by this extension. + // By default VSCode only shows that "Error running command " but not its text. + try { + await callback(...args); + } catch(e: unknown) { + telemetry.sendError(TelemetryErrorEventNames.UNCAUGHT_EXCEPTION, e as Error); + window.showErrorMessage(String(e)); + console.error(e); + } + }, thisArg); + + // When this extension is deactivated the disposables will be disposed. + _context.subscriptions.push(command); +} + diff --git a/src/commands/addKustomization.ts b/src/commands/addKustomization.ts index 4a9cda8f..0603d1f9 100644 --- a/src/commands/addKustomization.ts +++ b/src/commands/addKustomization.ts @@ -1,6 +1,6 @@ -import { openConfigureGitOpsWebview } from 'ui/webviews/configureGitOps/openWebview'; -import { SourceNode } from 'ui/treeviews/nodes/source/sourceNode'; -import { FluxSourceKinds } from 'types/flux/object'; +import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; +import { SourceNode } from '../views/nodes/sourceNode'; +import { FluxSourceKinds } from '../kubernetes/types/flux/object'; /** * Open ConfigureGitops webview with a source preselected (if user right-clicked a source node) diff --git a/src/commands/addSource.ts b/src/commands/addSource.ts index f66b35bb..d44a29e8 100644 --- a/src/commands/addSource.ts +++ b/src/commands/addSource.ts @@ -1,4 +1,4 @@ -import { openConfigureGitOpsWebview } from 'ui/webviews/configureGitOps/openWebview'; +import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; /** * Open ConfigureGitops webview with 'New Source' tab open diff --git a/src/commands/commands.ts b/src/commands/commands.ts deleted file mode 100644 index 307b8b5c..00000000 --- a/src/commands/commands.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { commands, Disposable, ExtensionContext, Uri, window } from 'vscode'; - -import { showOutputChannel } from 'cli/shell/output'; -import { refreshAllTreeViewsCommand, refreshResourcesTreeViewsCommand } from 'commands/refreshTreeViews'; -import { telemetry } from 'extension'; -import { CommandId } from 'types/extensionIds'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { addKustomization } from './addKustomization'; -import { addSource } from './addSource'; -import { copyResourceName } from './copyResourceName'; -import { createFromTemplate } from './createFromTemplate'; -import { createGitRepositoryForPath } from './createGitRepositoryForPath'; -import { createKustomizationForPath } from './createKustomizationForPath'; -import { deleteSource } from './deleteSource'; -import { deleteWorkload } from './deleteWorkload'; -import { fluxDisableGitOps, fluxEnableGitOps } from './enableDisableGitOps'; -import { expandAllSources, expandAllWorkloads } from './expandAll'; -import { fluxCheck } from './fluxCheck'; -import { checkFluxPrerequisites } from './fluxCheckPrerequisites'; -import { fluxReconcileRepositoryForPath } from './fluxReconcileGitRepositoryForPath'; -import { fluxReconcileSourceCommand } from './fluxReconcileSource'; -import { fluxReconcileWorkload, fluxReconcileWorkloadWithSource } from './fluxReconcileWorkload'; -import { installFluxCli } from './installFluxCli'; -import { kubectlApplyKustomization, kubectlApplyPath, kubectlDeletePath } from './kubectlApply'; -import { openInWgePortal } from './openInWgePortal'; -import { openKubeconfig, openResource } from './openResource'; -import { setPipelineAutoPromotion, setPipelineManualPromotion } from './pipelineAutoPromotion'; -import { pullGitRepository } from './pullGitRepository'; -import { resume } from './resume'; -import { setClusterProvider } from './setClusterProvider'; -import { setContextToGitopsCluster } from './setContextToGops'; -import { setCurrentKubernetesContext } from './setCurrentKubernetesContext'; -import { showGlobalState } from './showGlobalState'; -import { showInstalledVersions } from './showInstalledVersions'; -import { showLogs } from './showLogs'; -import { showNewUserGuide } from './showNewUserGuide'; -import { showWorkloadsHelpMessage } from './showWorkloadsHelpMessage'; -import { suspend } from './suspend'; -import { trace } from './trace'; - - -let _context: ExtensionContext; - -/** - * Registers GitOps extension commands. - * @param context VSCode extension context. - */ -export function registerCommands(context: ExtensionContext) { - _context = context; - - // kubectl - registerCommand(CommandId.SetCurrentKubernetesContext, setCurrentKubernetesContext); - - // flux - registerCommand(CommandId.Suspend, suspend); - registerCommand(CommandId.Resume, resume); - registerCommand(CommandId.CreateKustomization, createKustomizationForPath); - registerCommand(CommandId.FluxCheck, fluxCheck); - registerCommand(CommandId.FluxCheckPrerequisites, checkFluxPrerequisites); - registerCommand(CommandId.FluxReconcileSource, fluxReconcileSourceCommand); - registerCommand(CommandId.FluxReconcileRepository, fluxReconcileRepositoryForPath); - registerCommand(CommandId.FluxReconcileWorkloadWithSource, fluxReconcileWorkloadWithSource); - registerCommand(CommandId.FluxReconcileWorkload, fluxReconcileWorkload); - registerCommand(CommandId.FluxEnableGitOps, fluxEnableGitOps); - registerCommand(CommandId.FluxDisableGitOps, fluxDisableGitOps); - registerCommand(CommandId.FluxTrace, trace); - - // tree views - registerCommand(CommandId.SetClusterProvider, setClusterProvider); - registerCommand(CommandId.RefreshAllTreeViews, refreshAllTreeViewsCommand); - registerCommand(CommandId.RefreshResourcesTreeView, refreshResourcesTreeViewsCommand); - registerCommand(CommandId.PullGitRepository, pullGitRepository); - registerCommand(CommandId.CreateGitRepository, (fileExplorerUri?: Uri) => { - // only pass one argument when running from File Explorer context menu - createGitRepositoryForPath(fileExplorerUri); - }); - registerCommand(CommandId.ShowWorkloadsHelpMessage, showWorkloadsHelpMessage); - registerCommand(CommandId.DeleteWorkload, deleteWorkload); - registerCommand(CommandId.DeleteSource, deleteSource); - registerCommand(CommandId.CopyResourceName, copyResourceName); - registerCommand(CommandId.AddSource, addSource); - registerCommand(CommandId.AddKustomization, addKustomization); - registerCommand(CommandId.KubectlApplyPath, kubectlApplyPath); - registerCommand(CommandId.KubectlDeletePath, kubectlDeletePath); - registerCommand(CommandId.KubectlApplyKustomization, kubectlApplyKustomization); - - - - - registerCommand(CommandId.ExpandAllSources, expandAllSources); - registerCommand(CommandId.ExpandAllWorkloads, expandAllWorkloads); - - - // editor - registerCommand(CommandId.EditorOpenResource, openResource); - registerCommand(CommandId.EditorOpenKubeconfig, openKubeconfig); - - // webview - registerCommand(CommandId.ShowLogs, showLogs); - registerCommand(CommandId.ShowNewUserGuide, showNewUserGuide); - - // output - registerCommand(CommandId.ShowOutputChannel, showOutputChannel); - - // others - registerCommand(CommandId.ShowInstalledVersions, showInstalledVersions); - registerCommand(CommandId.InstallFluxCli, installFluxCli); - registerCommand(CommandId.ShowGlobalState, showGlobalState); - - // wget - registerCommand(CommandId.CreateFromTemplate, createFromTemplate); - registerCommand(CommandId.OpenInWgePortal, openInWgePortal); - registerCommand(CommandId.EnableAutoPromotion, setPipelineAutoPromotion); - registerCommand(CommandId.DisableAutoPromotion, setPipelineManualPromotion); - registerCommand(CommandId.SetContextToGitopsCluster, setContextToGitopsCluster); -} - -/** - * Registers vscode extension command. - * @param commandId Command identifier. - * @param callback Command handler. - * @param thisArg The `this` context used when invoking the handler function. - */ -function registerCommand(commandId: string, callback: (...args: any[])=> any, thisArg?: any): void { - - const command: Disposable = commands.registerCommand(commandId, async(...args) => { - - // Show error in console when it happens in any of the commands registered by this extension. - // By default VSCode only shows that "Error running command " but not its text. - try { - await callback(...args); - } catch(e: unknown) { - telemetry.sendError(TelemetryError.UNCAUGHT_EXCEPTION, e as Error); - window.showErrorMessage(String(e)); - console.error(e); - } - }, thisArg); - - // When this extension is deactivated the disposables will be disposed. - _context.subscriptions.push(command); -} - diff --git a/src/commands/copyResourceName.ts b/src/commands/copyResourceName.ts index 0c68dfa0..55ceb8c2 100644 --- a/src/commands/copyResourceName.ts +++ b/src/commands/copyResourceName.ts @@ -1,7 +1,6 @@ import { env } from 'vscode'; - -import { SourceNode } from 'ui/treeviews/nodes/source/sourceNode'; -import { WorkloadNode } from 'ui/treeviews/nodes/workload/workloadNode'; +import { SourceNode } from '../views/nodes/sourceNode'; +import { WorkloadNode } from '../views/nodes/workloadNode'; /** * Copy to clipboard any resource node name. diff --git a/src/commands/createFromTemplate.ts b/src/commands/createFromTemplate.ts index 6aaabb9c..4486263d 100644 --- a/src/commands/createFromTemplate.ts +++ b/src/commands/createFromTemplate.ts @@ -1,5 +1,6 @@ -import { GitOpsTemplateNode } from 'ui/treeviews/nodes/wge/gitOpsTemplateNode'; -import { openCreateFromTemplatePanel } from 'ui/webviews/createFromTemplate/openWebview'; +import * as vscode from 'vscode'; +import { GitOpsTemplateNode } from '../views/nodes/gitOpsTemplateNode'; +import { openCreateFromTemplatePanel } from '../webview-backend/createFromTemplate/openWebview'; export async function createFromTemplate(templateNode?: GitOpsTemplateNode) { // const name = await window.showQuickPick(['cluster-template-development', 'cluster-template-development-plus-kubelogin']); diff --git a/src/commands/createGitRepositoryForPath.ts b/src/commands/createGitRepositoryForPath.ts index 460607b7..bcb2db0b 100644 --- a/src/commands/createGitRepositoryForPath.ts +++ b/src/commands/createGitRepositoryForPath.ts @@ -1,10 +1,10 @@ +import gitUrlParse from 'git-url-parse'; import { Uri, window, workspace } from 'vscode'; - -import { checkGitVersion } from 'cli/checkVersions'; -import { getFolderGitInfo } from 'cli/git/gitInfo'; -import { failed } from 'types/errorable'; -import { getCurrentClusterInfo } from 'ui/treeviews/treeViews'; -import { openConfigureGitOpsWebview } from 'ui/webviews/configureGitOps/openWebview'; +import { failed } from '../errorable'; +import { getFolderGitInfo } from '../git/gitInfo'; +import { checkGitVersion } from '../install'; +import { getCurrentClusterInfo } from '../views/treeViews'; +import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; /** * Add git repository source whether from an opened folder diff --git a/src/commands/createKustomizationForPath.ts b/src/commands/createKustomizationForPath.ts index f71979e0..e6dc78cc 100644 --- a/src/commands/createKustomizationForPath.ts +++ b/src/commands/createKustomizationForPath.ts @@ -1,11 +1,10 @@ import path from 'path'; import { Uri, window, workspace } from 'vscode'; - -import { getFolderGitInfo, getGitRepositoryforGitInfo } from 'cli/git/gitInfo'; -import { failed } from 'types/errorable'; -import { namespacedFluxObject } from 'utils/namespacedFluxObject'; -import { getCurrentClusterInfo } from 'ui/treeviews/treeViews'; -import { openConfigureGitOpsWebview } from 'ui/webviews/configureGitOps/openWebview'; +import { failed } from '../errorable'; +import { getFolderGitInfo, getGitRepositoryforGitInfo } from '../git/gitInfo'; +import { namespacedObject } from '../kubernetes/types/flux/object'; +import { getCurrentClusterInfo } from '../views/treeViews'; +import { openConfigureGitOpsWebview } from '../webview-backend/configureGitOps/openWebview'; /** * Create kustomization from File Explorer context menu @@ -54,7 +53,7 @@ export async function createKustomizationForPath(fileExplorerUri?: Uri): Promise const gr = await getGitRepositoryforGitInfo(gitInfo); const selectSource = !!gr; - let sourceName = namespacedFluxObject(gr) || ''; + let sourceName = namespacedObject(gr) || ''; openConfigureGitOpsWebview(selectSource, sourceName, { kustomization: { diff --git a/src/commands/createSource.ts b/src/commands/createSource.ts index 3b81e333..7cce443d 100644 --- a/src/commands/createSource.ts +++ b/src/commands/createSource.ts @@ -1,7 +1,8 @@ import gitUrlParse from 'git-url-parse'; import { commands, env, Uri, window } from 'vscode'; +import { azureTools } from '../azure/azureTools'; +import { CommandId } from '../commands'; -import { CommandId } from 'types/extensionIds'; /** * Show notifications reminding users to add a public key diff --git a/src/commands/deleteSource.ts b/src/commands/deleteSource.ts index b4eb4583..0cacf529 100644 --- a/src/commands/deleteSource.ts +++ b/src/commands/deleteSource.ts @@ -1,18 +1,16 @@ import { window } from 'vscode'; - -import { AzureClusterProvider, azureTools } from 'cli/azure/azureTools'; -import { fluxTools } from 'cli/flux/fluxTools'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { telemetry } from 'extension'; -import { failed } from 'types/errorable'; -import { FluxSource } from 'types/fluxCliTypes'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryEvent } from 'types/telemetryEventNames'; -import { BucketNode } from 'ui/treeviews/nodes/source/bucketNode'; -import { GitRepositoryNode } from 'ui/treeviews/nodes/source/gitRepositoryNode'; -import { HelmRepositoryNode } from 'ui/treeviews/nodes/source/helmRepositoryNode'; -import { OCIRepositoryNode } from 'ui/treeviews/nodes/source/ociRepositoryNode'; -import { getCurrentClusterInfo, reloadSourcesTreeView, reloadWorkloadsTreeView } from 'ui/treeviews/treeViews'; +import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; +import { failed } from '../errorable'; +import { telemetry } from '../extension'; +import { fluxTools } from '../flux/fluxTools'; +import { FluxSource } from '../flux/fluxTypes'; +import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; +import { TelemetryEventNames } from '../telemetry'; +import { BucketNode } from '../views/nodes/bucketNode'; +import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; +import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; +import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; +import { getCurrentClusterInfo, refreshSourcesTreeView, refreshWorkloadsTreeView } from '../views/treeViews'; /** * Delete a source @@ -21,14 +19,14 @@ import { getCurrentClusterInfo, reloadSourcesTreeView, reloadWorkloadsTreeView } */ export async function deleteSource(sourceNode: GitRepositoryNode | OCIRepositoryNode | HelmRepositoryNode | BucketNode) { - const sourceName = sourceNode.resource.metadata.name; + const sourceName = sourceNode.resource.metadata.name || ''; const sourceNamespace = sourceNode.resource.metadata.namespace || ''; const confirmButton = 'Delete'; - const sourceType: FluxSource | 'unknown' = sourceNode.resource.kind === Kind.GitRepository ? 'source git' : - sourceNode.resource.kind === Kind.HelmRepository ? 'source helm' : - sourceNode.resource.kind === Kind.OCIRepository ? 'source oci' : - sourceNode.resource.kind === Kind.Bucket ? 'source bucket' : 'unknown'; + const sourceType: FluxSource | 'unknown' = sourceNode.resource.kind === KubernetesObjectKinds.GitRepository ? 'source git' : + sourceNode.resource.kind === KubernetesObjectKinds.HelmRepository ? 'source helm' : + sourceNode.resource.kind === KubernetesObjectKinds.OCIRepository ? 'source oci' : + sourceNode.resource.kind === KubernetesObjectKinds.Bucket ? 'source bucket' : 'unknown'; if (sourceType === 'unknown') { window.showErrorMessage(`Unknown Source resource kind ${sourceNode.resource.kind}`); @@ -42,22 +40,21 @@ export async function deleteSource(sourceNode: GitRepositoryNode | OCIRepository return; } - telemetry.send(TelemetryEvent.DeleteSource, { + telemetry.send(TelemetryEventNames.DeleteSource, { kind: sourceNode.resource.kind, }); const currentClusterInfo = await getCurrentClusterInfo(); - const contextName = kubeConfig.getCurrentContext(); if (failed(currentClusterInfo)) { return; } if (currentClusterInfo.result.isAzure) { - await azureTools.deleteSource(sourceName, contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); - reloadWorkloadsTreeView(); + await azureTools.deleteSource(sourceName, currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); + refreshWorkloadsTreeView(); } else { await fluxTools.delete(sourceType, sourceName, sourceNamespace); } - reloadSourcesTreeView(); + refreshSourcesTreeView(); } diff --git a/src/commands/deleteWorkload.ts b/src/commands/deleteWorkload.ts index 947f3bc5..ff54f353 100644 --- a/src/commands/deleteWorkload.ts +++ b/src/commands/deleteWorkload.ts @@ -1,16 +1,14 @@ import { window } from 'vscode'; - -import { AzureClusterProvider, azureTools } from 'cli/azure/azureTools'; -import { fluxTools } from 'cli/flux/fluxTools'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { telemetry } from 'extension'; -import { failed } from 'types/errorable'; -import { FluxWorkload } from 'types/fluxCliTypes'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryEvent } from 'types/telemetryEventNames'; -import { HelmReleaseNode } from 'ui/treeviews/nodes/workload/helmReleaseNode'; -import { KustomizationNode } from 'ui/treeviews/nodes/workload/kustomizationNode'; -import { getCurrentClusterInfo, reloadWorkloadsTreeView } from 'ui/treeviews/treeViews'; +import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; +import { failed } from '../errorable'; +import { telemetry } from '../extension'; +import { fluxTools } from '../flux/fluxTools'; +import { FluxWorkload } from '../flux/fluxTypes'; +import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; +import { TelemetryEventNames } from '../telemetry'; +import { KustomizationNode } from '../views/nodes/kustomizationNode'; +import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; +import { getCurrentClusterInfo, refreshWorkloadsTreeView } from '../views/treeViews'; /** @@ -20,18 +18,18 @@ import { getCurrentClusterInfo, reloadWorkloadsTreeView } from 'ui/treeviews/tre */ export async function deleteWorkload(workloadNode: KustomizationNode | HelmReleaseNode) { - const workloadName = workloadNode.resource.metadata.name; + const workloadName = workloadNode.resource.metadata.name || ''; const workloadNamespace = workloadNode.resource.metadata.namespace || ''; const confirmButton = 'Delete'; let workloadType: FluxWorkload; switch(workloadNode.resource.kind) { - case Kind.Kustomization: { + case KubernetesObjectKinds.Kustomization: { workloadType = 'kustomization'; break; } - case Kind.HelmRelease: { + case KubernetesObjectKinds.HelmRelease: { workloadType = 'helmrelease'; break; } @@ -42,9 +40,7 @@ export async function deleteWorkload(workloadNode: KustomizationNode | HelmRelea } const currentClusterInfo = await getCurrentClusterInfo(); - const contextName = kubeConfig.getCurrentContext(); - - if (failed(currentClusterInfo) || !contextName) { + if (failed(currentClusterInfo)) { return; } @@ -53,6 +49,8 @@ export async function deleteWorkload(workloadNode: KustomizationNode | HelmRelea return; } + + const pressedButton = await window.showWarningMessage(`Do you want to delete ${workloadNode.resource.kind} "${workloadName}"?`, { modal: true, }, confirmButton); @@ -60,7 +58,7 @@ export async function deleteWorkload(workloadNode: KustomizationNode | HelmRelea return; } - telemetry.send(TelemetryEvent.DeleteWorkload, { + telemetry.send(TelemetryEventNames.DeleteWorkload, { kind: workloadNode.resource.kind, }); @@ -68,10 +66,10 @@ export async function deleteWorkload(workloadNode: KustomizationNode | HelmRelea if (currentClusterInfo.result.isAzure && workloadType === 'kustomization') { const fluxConfigName = (workloadNode.resource.spec as any).sourceRef?.name; const azResourceName = azureTools.getAzName(fluxConfigName, workloadName); - await azureTools.deleteKustomization(fluxConfigName, azResourceName, contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); + await azureTools.deleteKustomization(fluxConfigName, azResourceName, currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); } else { await fluxTools.delete(workloadType, workloadName, workloadNamespace); } - reloadWorkloadsTreeView(); + refreshWorkloadsTreeView(); } diff --git a/src/commands/enableDisableGitOps.ts b/src/commands/enableDisableGitOps.ts index 9dd1ea72..0ce8920b 100644 --- a/src/commands/enableDisableGitOps.ts +++ b/src/commands/enableDisableGitOps.ts @@ -1,14 +1,13 @@ import { window } from 'vscode'; - -import { azureTools, isAzureProvider } from 'cli/azure/azureTools'; -import { fluxTools } from 'cli/flux/fluxTools'; -import { detectClusterProvider } from 'cli/kubernetes/clusterProvider'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { refreshAllTreeViewsCommand } from 'commands/refreshTreeViews'; -import { skipConfirmations, telemetry } from 'extension'; -import { ClusterProvider } from 'types/kubernetes/clusterProvider'; -import { TelemetryEvent } from 'types/telemetryEventNames'; -import { ClusterNode } from 'ui/treeviews/nodes/cluster/clusterNode'; +import { azureTools, isAzureProvider } from '../azure/azureTools'; +import { failed } from '../errorable'; +import { telemetry, disableConfirmations } from '../extension'; +import { fluxTools } from '../flux/fluxTools'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { ClusterProvider } from '../kubernetes/types/kubernetesTypes'; +import { TelemetryEventNames } from '../telemetry'; +import { ClusterContextNode } from '../views/nodes/clusterContextNode'; +import { getCurrentClusterInfo, refreshAllTreeViews } from '../views/treeViews'; @@ -17,14 +16,23 @@ import { ClusterNode } from 'ui/treeviews/nodes/cluster/clusterNode'; * @param clusterNode target cluster tree view item * @param enableGitOps Specifies if function should install or uninstall */ -async function enableDisableGitOps(clusterNode: ClusterNode | undefined, enableGitOps: boolean) { - let context = clusterNode?.context; - let cluster = clusterNode?.cluster; +async function enableDisableGitOps(clusterNode: ClusterContextNode | undefined, enableGitOps: boolean) { + + let contextName = clusterNode?.contextName || ''; + let clusterName = clusterNode?.clusterName || ''; + + if (!clusterNode) { + // was executed from the welcome view - get current context + const currentClusterInfo = await getCurrentClusterInfo(); + if (failed(currentClusterInfo)) { + return; + } - const contextName = context?.name || kubeConfig.getCurrentContext(); - const clusterName = cluster?.name || kubeConfig.getCurrentCluster()?.name; + contextName = currentClusterInfo.result.contextName; + clusterName = currentClusterInfo.result.clusterName; + } - const clusterProvider = await detectClusterProvider(contextName); + const clusterProvider = await kubernetesTools.detectClusterProvider(contextName); if (clusterProvider === ClusterProvider.Unknown) { window.showErrorMessage('Cluster provider not detected yet.'); @@ -34,30 +42,21 @@ async function enableDisableGitOps(clusterNode: ClusterNode | undefined, enableG return; } - if(!skipConfirmations) { - if(!enableGitOps) { - const confirm = await window.showWarningMessage(`Do you want to disable GitOps (run \`flux uninstall\`) on the "${clusterName}" cluster?`, { - modal: true, - }, 'Disable'); - if (confirm !== 'Disable') { - return; - } - } else { - const confirm = await window.showInformationMessage(`Do you want to enable GitOps (run \`flux install\`) on the "${clusterName}" cluster?`, { - modal: true, - }, 'Enable'); - if (confirm !== 'Enable') { - return; - } + if(!disableConfirmations && !enableGitOps ) { + const confirm = await window.showWarningMessage(`Do you want to disable GitOps on the "${clusterName}" cluster?`, { + modal: true, + }, 'Disable'); + if (confirm !== 'Disable') { + return; } } if (enableGitOps) { - telemetry.send(TelemetryEvent.EnableGitOps, { + telemetry.send(TelemetryEventNames.EnableGitOps, { clusterProvider, }); } else { - telemetry.send(TelemetryEvent.DisableGitOps, { + telemetry.send(TelemetryEventNames.DisableGitOps, { clusterProvider, }); } @@ -78,14 +77,14 @@ async function enableDisableGitOps(clusterNode: ClusterNode | undefined, enableG } // Refresh now that flux is installed or uninstalled - refreshAllTreeViewsCommand(); + refreshAllTreeViews(); } /** * Install flux to the passed or current cluster (if first argument is undefined) * @param clusterNode target cluster tree node */ -export async function fluxEnableGitOps(clusterNode: ClusterNode | undefined) { +export async function fluxEnableGitOps(clusterNode: ClusterContextNode | undefined) { return await enableDisableGitOps(clusterNode, true); } @@ -93,6 +92,6 @@ export async function fluxEnableGitOps(clusterNode: ClusterNode | undefined) { * Uninstall flux from the passed or current cluster (if first argument is undefined) * @param clusterNode target cluster tree node */ -export async function fluxDisableGitOps(clusterNode: ClusterNode | undefined) { +export async function fluxDisableGitOps(clusterNode: ClusterContextNode | undefined) { return await enableDisableGitOps(clusterNode, false); } diff --git a/src/commands/expandAll.ts b/src/commands/expandAll.ts deleted file mode 100644 index 87a5e096..00000000 --- a/src/commands/expandAll.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { sourceDataProvider, workloadDataProvider } from 'ui/treeviews/treeViews'; - -export async function expandAllSources() { - sourceDataProvider.expandAll(); -} - -export async function expandAllWorkloads() { - workloadDataProvider.expandAll(); -} diff --git a/src/commands/fluxCheck.ts b/src/commands/fluxCheck.ts index 55c2ec20..aac969d7 100644 --- a/src/commands/fluxCheck.ts +++ b/src/commands/fluxCheck.ts @@ -1,12 +1,11 @@ import safesh from 'shell-escape-tag'; - -import * as shell from 'cli/shell/exec'; -import { ClusterNode } from 'ui/treeviews/nodes/cluster/clusterNode'; +import { shell } from '../shell'; +import { ClusterContextNode } from '../views/nodes/clusterContextNode'; /** * Runs `flux check` command for selected cluster in the output view. * @param clusterNode target cluster node (from tree node context menu) */ -export async function fluxCheck(clusterNode: ClusterNode) { - shell.execWithOutput(safesh`flux check --context ${clusterNode.context.name}`); +export async function fluxCheck(clusterNode: ClusterContextNode) { + shell.execWithOutput(safesh`flux check --context ${clusterNode.contextName}`); } diff --git a/src/commands/fluxCheckPrerequisites.ts b/src/commands/fluxCheckPrerequisites.ts index a05b2add..a430ab56 100644 --- a/src/commands/fluxCheckPrerequisites.ts +++ b/src/commands/fluxCheckPrerequisites.ts @@ -1,4 +1,4 @@ -import * as shell from 'cli/shell/exec'; +import { shell } from '../shell'; /** * Runs `flux check --pre` command in the output view. diff --git a/src/commands/fluxReconcileGitRepositoryForPath.ts b/src/commands/fluxReconcileGitRepositoryForPath.ts index 0e4de782..e02dbb75 100644 --- a/src/commands/fluxReconcileGitRepositoryForPath.ts +++ b/src/commands/fluxReconcileGitRepositoryForPath.ts @@ -1,7 +1,6 @@ import { Uri, window } from 'vscode'; - -import { fluxTools } from 'cli/flux/fluxTools'; -import { getFolderGitInfo, getGitRepositoryforGitInfo } from 'cli/git/gitInfo'; +import { fluxTools } from '../flux/fluxTools'; +import { getFolderGitInfo, getGitRepositoryforGitInfo } from '../git/gitInfo'; /** * Command to reconcile GitRepository for selected file @@ -14,7 +13,7 @@ export async function fluxReconcileRepositoryForPath(fileExplorerUri?: Uri) { const gitInfo = await getFolderGitInfo(fileExplorerUri.fsPath); const gr = await getGitRepositoryforGitInfo(gitInfo); - if(!gr?.metadata.name || !gr.metadata.namespace) { + if(!gr?.metadata?.name || !gr.metadata?.namespace) { window.showWarningMessage(`No GitRepository with url '${gitInfo?.url}'`); return; } diff --git a/src/commands/fluxReconcileSource.ts b/src/commands/fluxReconcileSource.ts index 57d53841..9631cbd9 100644 --- a/src/commands/fluxReconcileSource.ts +++ b/src/commands/fluxReconcileSource.ts @@ -1,12 +1,12 @@ import { window } from 'vscode'; - -import { fluxTools } from 'cli/flux/fluxTools'; -import { FluxSource } from 'types/fluxCliTypes'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { BucketNode } from 'ui/treeviews/nodes/source/bucketNode'; -import { GitRepositoryNode } from 'ui/treeviews/nodes/source/gitRepositoryNode'; -import { HelmRepositoryNode } from 'ui/treeviews/nodes/source/helmRepositoryNode'; -import { OCIRepositoryNode } from 'ui/treeviews/nodes/source/ociRepositoryNode'; +import { fluxTools } from '../flux/fluxTools'; +import { FluxSource } from '../flux/fluxTypes'; +import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; +import { BucketNode } from '../views/nodes/bucketNode'; +import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; +import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; +import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; +import { refreshSourcesTreeView } from '../views/treeViews'; /** * Invoke flux reconcile of a specific source. @@ -14,15 +14,17 @@ import { OCIRepositoryNode } from 'ui/treeviews/nodes/source/ociRepositoryNode'; */ export async function fluxReconcileSourceCommand(source: GitRepositoryNode | OCIRepositoryNode | HelmRepositoryNode | BucketNode): Promise { - const sourceType: FluxSource | 'unknown' = source.resource.kind === Kind.GitRepository ? 'source git' : - source.resource.kind === Kind.OCIRepository ? 'source oci' : - source.resource.kind === Kind.HelmRepository ? 'source helm' : - source.resource.kind === Kind.Bucket ? 'source bucket' : 'unknown'; + const sourceType: FluxSource | 'unknown' = source.resource.kind === KubernetesObjectKinds.GitRepository ? 'source git' : + source.resource.kind === KubernetesObjectKinds.OCIRepository ? 'source oci' : + source.resource.kind === KubernetesObjectKinds.HelmRepository ? 'source helm' : + source.resource.kind === KubernetesObjectKinds.Bucket ? 'source bucket' : 'unknown'; if (sourceType === 'unknown') { window.showErrorMessage(`Unknown Flux Source resource kind ${source.resource.kind}`); return; } - await fluxTools.reconcile(sourceType, source.resource.metadata.name, source.resource.metadata.namespace || ''); + await fluxTools.reconcile(sourceType, source.resource.metadata.name || '', source.resource.metadata.namespace || ''); + + refreshSourcesTreeView(); } diff --git a/src/commands/fluxReconcileWorkload.ts b/src/commands/fluxReconcileWorkload.ts index b7c8cffd..4888ca2e 100644 --- a/src/commands/fluxReconcileWorkload.ts +++ b/src/commands/fluxReconcileWorkload.ts @@ -1,31 +1,28 @@ import { window } from 'vscode'; - -import { fluxTools } from 'cli/flux/fluxTools'; -import { FluxWorkload } from 'types/fluxCliTypes'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { HelmReleaseNode } from 'ui/treeviews/nodes/workload/helmReleaseNode'; -import { KustomizationNode } from 'ui/treeviews/nodes/workload/kustomizationNode'; +import { fluxTools } from '../flux/fluxTools'; +import { FluxWorkload } from '../flux/fluxTypes'; +import { KubernetesObjectKinds } from '../kubernetes/types/kubernetesTypes'; +import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; +import { KustomizationNode } from '../views/nodes/kustomizationNode'; +import { refreshWorkloadsTreeView } from '../views/treeViews'; /** * Invoke flux reconcile of a specific workload. * @param workload Target workload tree view item. */ -export async function fluxReconcileWorkload(workload: KustomizationNode | HelmReleaseNode, withSource = false): Promise { +export async function fluxReconcileWorkload(workload: KustomizationNode | HelmReleaseNode): Promise { /** * Accepted workload names in flux: `kustomization`, `helmrelease`. * Can be checked with: `flux reconcile --help` */ - const workloadType: FluxWorkload | 'unknown' = workload.resource.kind === Kind.Kustomization ? 'kustomization' : - workload.resource.kind === Kind.HelmRelease ? 'helmrelease' : 'unknown'; + const workloadType: FluxWorkload | 'unknown' = workload.resource.kind === KubernetesObjectKinds.Kustomization ? 'kustomization' : + workload.resource.kind === KubernetesObjectKinds.HelmRelease ? 'helmrelease' : 'unknown'; if (workloadType === 'unknown') { window.showErrorMessage(`Unknown Workload resource kind ${workload.resource.kind}`); return; } - await fluxTools.reconcile(workloadType, workload.resource.metadata.name, workload.resource.metadata.namespace || '', withSource); -} - + await fluxTools.reconcile(workloadType, workload.resource.metadata.name || '', workload.resource.metadata.namespace || ''); -export async function fluxReconcileWorkloadWithSource(workload: KustomizationNode | HelmReleaseNode): Promise { - fluxReconcileWorkload(workload, true); + refreshWorkloadsTreeView(); } diff --git a/src/commands/installFluxCli.ts b/src/commands/installFluxCli.ts index f52c893f..a810bcad 100644 --- a/src/commands/installFluxCli.ts +++ b/src/commands/installFluxCli.ts @@ -5,16 +5,14 @@ import os from 'os'; import path from 'path'; import request from 'request'; import { commands, window } from 'vscode'; - -import * as shell from 'cli/shell/exec'; -import { Platform } from 'cli/shell/exec'; -import { output } from 'cli/shell/output'; -import { runTerminalCommand } from 'cli/shell/terminal'; -import { refreshAllTreeViewsCommand } from 'commands/refreshTreeViews'; -import { GlobalStateKey } from 'data/globalState'; -import { globalState } from 'extension'; -import { Errorable, failed } from 'types/errorable'; -import { appendToPathEnvironmentVariableWindows, createDir, deleteFile, downloadFile, getAppdataPath, moveFile, readFile, unzipFile } from 'utils/fsUtils'; +import { Errorable, failed, succeeded } from '../errorable'; +import { globalState } from '../extension'; +import { GlobalStateKey } from '../globalState'; +import { output } from '../output'; +import { Platform, shell, shellCodeError } from '../shell'; +import { runTerminalCommand } from '../terminal'; +import { appendToPathEnvironmentVariableWindows, createDir, deleteFile, downloadFile, getAppdataPath, moveFile, readFile, unzipFile } from '../utils/fsUtils'; +import { refreshAllTreeViews } from '../views/treeViews'; const fluxGitHubUserProject = 'fluxcd/flux2'; @@ -240,7 +238,7 @@ export async function installFluxCli() { output.send(`✔ Flux ${latestFluxVersionResult.result} successfully installed`); - refreshAllTreeViewsCommand(); + refreshAllTreeViews(); showNotificationToReloadTheEditor(); return; diff --git a/src/commands/kubectlApply.ts b/src/commands/kubectlApply.ts deleted file mode 100644 index 7eb4d631..00000000 --- a/src/commands/kubectlApply.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as shell from 'cli/shell/exec'; -import { Uri, window } from 'vscode'; - -export async function kubectlApplyPath(uri?: Uri) { - uri ??= window.activeTextEditor?.document.uri; - if(uri) { - return await shell.execWithOutput(`kubectl apply -f ${uri.fsPath}`); - } -} -export async function kubectlDeletePath(uri?: Uri) { - uri ??= window.activeTextEditor?.document.uri; - if(uri) { - return await shell.execWithOutput(`kubectl delete -f ${uri.fsPath}`); - } -} - -export async function kubectlApplyKustomization(uri?: Uri) { - if(uri) { - return await shell.execWithOutput(`kubectl apply -k ${uri.fsPath}`); - } -} diff --git a/src/commands/openInWgePortal.ts b/src/commands/openInWgePortal.ts deleted file mode 100644 index 6e129a5e..00000000 --- a/src/commands/openInWgePortal.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { currentContextData } from 'data/contextData'; -import { CanaryNode } from 'ui/treeviews/nodes/wge/canaryNode'; -import { GitOpsSetNode } from 'ui/treeviews/nodes/wge/gitOpsSetNode'; -import { GitOpsTemplateNode } from 'ui/treeviews/nodes/wge/gitOpsTemplateNode'; -import { PipelineNode } from 'ui/treeviews/nodes/wge/pipelineNode'; -import { WgeContainerNode } from 'ui/treeviews/nodes/wge/wgeNodes'; -import { env, Uri } from 'vscode'; - - -type WgePortalNode = GitOpsTemplateNode | PipelineNode | CanaryNode | GitOpsSetNode | WgeContainerNode; - -export function openInWgePortal(node: WgePortalNode) { - const portalUrl = currentContextData().portalUrl; - if(!portalUrl) { - return; - } - - const query = node.wgePortalQuery; - const url = `${portalUrl}/${query}`; - - // const url = `https://${portalHost}/canary_details/details?clusterName=vcluster-howard-moomboo-stage%2Fhoward-moomboo-staging&name=${name}&namespace=${namespace}`; - env.openExternal(Uri.parse(url)); -} - diff --git a/src/commands/openResource.ts b/src/commands/openResource.ts index 7a3aa461..6c578608 100644 --- a/src/commands/openResource.ts +++ b/src/commands/openResource.ts @@ -1,8 +1,6 @@ import { Uri, window, workspace } from 'vscode'; - -import { kubeConfigPath } from 'cli/kubernetes/kubernetesConfigWatcher'; -import { telemetry } from 'extension'; -import { TelemetryError } from 'types/telemetryEventNames'; +import { telemetry } from '../extension'; +import { TelemetryErrorEventNames } from '../telemetry'; /** * Open resource in the editor @@ -16,12 +14,6 @@ export async function openResource(uri: Uri): Promise { }, error => { window.showErrorMessage(`Error loading document: ${error}`); - telemetry.sendError(TelemetryError.FAILED_TO_OPEN_RESOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_OPEN_RESOURCE); }); } - -export async function openKubeconfig() { - if (kubeConfigPath) { - openResource(Uri.file(kubeConfigPath)); - } -} diff --git a/src/commands/pipelineAutoPromotion.ts b/src/commands/pipelineAutoPromotion.ts deleted file mode 100644 index 0e0d69d6..00000000 --- a/src/commands/pipelineAutoPromotion.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { kubectlPatchNamespacedResource } from 'cli/kubernetes/kubernetesToolsKubectl'; -import { PipelineNode } from 'ui/treeviews/nodes/wge/pipelineNode'; - -export async function setPipelineAutoPromotion(node: PipelineNode) { - await kubectlPatchNamespacedResource(node.resource, '{"spec": {"promotion": {"manual": false}}}'); - - node.dataProvider.reload(); -} - -export async function setPipelineManualPromotion(node: PipelineNode) { - await kubectlPatchNamespacedResource(node.resource, '{"spec": {"promotion": {"manual": true}}}'); - - node.dataProvider.reload(); -} diff --git a/src/commands/pullGitRepository.ts b/src/commands/pullGitRepository.ts index a5d88e31..5d1199d5 100644 --- a/src/commands/pullGitRepository.ts +++ b/src/commands/pullGitRepository.ts @@ -3,13 +3,12 @@ import semverGt from 'semver/functions/gt'; import semverSatisfies from 'semver/functions/satisfies'; import safesh from 'shell-escape-tag'; import { commands, Uri, window } from 'vscode'; - -import { checkGitVersion } from 'cli/checkVersions'; -import * as shell from 'cli/shell/exec'; -import { telemetry } from 'extension'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { GitRepositoryNode } from 'ui/treeviews/nodes/source/gitRepositoryNode'; -import { quoteFsPath } from 'utils/fsUtils'; +import { telemetry } from '../extension'; +import { checkGitVersion } from '../install'; +import { shell } from '../shell'; +import { TelemetryErrorEventNames } from '../telemetry'; +import { quoteFsPath } from '../utils/fsUtils'; +import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; /** * Clone git source repository to user machine from @@ -34,7 +33,7 @@ export async function pullGitRepository(sourceNode: GitRepositoryNode): Promise< return; } - const pickedFolderFsPath = path.join(pickedFolder[0].fsPath, sourceNode.resource.metadata.name); + const pickedFolderFsPath = path.join(pickedFolder[0].fsPath, sourceNode.resource.metadata.name || 'gitRepository'); // precedence - commit > semver > tag > branch const url = safesh.escape(sourceNode.resource.spec.url); @@ -61,7 +60,7 @@ export async function pullGitRepository(sourceNode: GitRepositoryNode): Promise< const gitCloneShellResult = await shell.execWithOutput(query); if (gitCloneShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_GIT_CLONE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_GIT_CLONE); window.showErrorMessage(gitCloneShellResult?.stderr || ''); return; } @@ -90,7 +89,7 @@ async function getLatestTagFromSemver(url: string, semver: string): Promise tag.length); if (!tags.length) { - telemetry.sendError(TelemetryError.FAILED_TO_PARSE_GIT_TAGS_FROM_OUTPUT); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_PARSE_GIT_TAGS_FROM_OUTPUT); window.showErrorMessage(`No tags found in ${url}`); return; } diff --git a/src/commands/refreshTreeViews.ts b/src/commands/refreshTreeViews.ts deleted file mode 100644 index 5c63e3b9..00000000 --- a/src/commands/refreshTreeViews.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { syncKubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { reloadClustersTreeView, reloadSourcesTreeView, reloadWgeTreeView, reloadWorkloadsTreeView, sourceDataProvider, wgeDataProvider, workloadDataProvider } from '../ui/treeviews/treeViews'; - -/** - * Clicked button on the cluster tree view - */ - -export async function refreshAllTreeViewsCommand() { - await syncKubeConfig(true); - refreshAllTreeViews(); -} - -export async function refreshAllTreeViews() { - - reloadClustersTreeView(); - refreshResourcesTreeViews(); -} - - -/** - * Clicked button on the sources or workloads tree view - */ -export function refreshResourcesTreeViewsCommand() { - refreshResourcesTreeViews(); -} - -export function refreshResourcesTreeViews() { - reloadSourcesTreeView(); - reloadWorkloadsTreeView(); - reloadWgeTreeView(); -} - -export function redrawResourcesTreeViews() { - sourceDataProvider.redraw(); - workloadDataProvider.redraw(); - wgeDataProvider.redraw(); -} diff --git a/src/commands/resume.ts b/src/commands/resume.ts index 7a08e5b8..58496056 100644 --- a/src/commands/resume.ts +++ b/src/commands/resume.ts @@ -1,14 +1,55 @@ +import { window } from 'vscode'; +import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; +import { failed } from '../errorable'; +import { fluxTools } from '../flux/fluxTools'; +import { FluxSource, FluxWorkload } from '../flux/fluxTypes'; +import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; +import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; +import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; +import { KustomizationNode } from '../views/nodes/kustomizationNode'; +import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; +import { getCurrentClusterInfo, refreshSourcesTreeView, refreshWorkloadsTreeView } from '../views/treeViews'; -import { kubectlPatchNamespacedResource } from 'cli/kubernetes/kubernetesToolsKubectl'; -import { GitRepositoryNode } from 'ui/treeviews/nodes/source/gitRepositoryNode'; -import { HelmRepositoryNode } from 'ui/treeviews/nodes/source/helmRepositoryNode'; -import { GitOpsSetNode } from 'ui/treeviews/nodes/wge/gitOpsSetNode'; -import { HelmReleaseNode } from 'ui/treeviews/nodes/workload/helmReleaseNode'; -import { KustomizationNode } from 'ui/treeviews/nodes/workload/kustomizationNode'; +/** + * Resume source or workload reconciliation and refresh its Tree View. + * + * @param node sources tree view node + */ +export async function resume(node: GitRepositoryNode | HelmReleaseNode | HelmRepositoryNode | KustomizationNode) { -export async function resume(node: GitRepositoryNode | HelmReleaseNode | KustomizationNode | HelmRepositoryNode | GitOpsSetNode) { - await kubectlPatchNamespacedResource(node.resource, '{"spec": {"suspend": false}}'); + const currentClusterInfo = await getCurrentClusterInfo(); + if (failed(currentClusterInfo)) { + return; + } - node.dataProvider.reload(); -} + const fluxResourceType: FluxSource | FluxWorkload | 'unknown' = node instanceof GitRepositoryNode ? + 'source git' : node instanceof HelmRepositoryNode ? + 'source helm' : node instanceof OCIRepositoryNode ? + 'source oci' : node instanceof HelmReleaseNode ? + 'helmrelease' : node instanceof KustomizationNode ? + 'kustomization' : 'unknown'; + if (fluxResourceType === 'unknown') { + window.showErrorMessage(`Unknown object kind ${fluxResourceType}`); + return; + } + + if (currentClusterInfo.result.isAzure) { + // TODO: implement + if (fluxResourceType === 'helmrelease' || fluxResourceType === 'kustomization') { + window.showInformationMessage('Not implemented on AKS/ARC', { modal: true }); + return; + } + await azureTools.resume(node.resource.metadata.name || '', currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); + } else { + await fluxTools.resume(fluxResourceType, node.resource.metadata.name || '', node.resource.metadata.namespace || ''); + } + if (node instanceof GitRepositoryNode || node instanceof OCIRepositoryNode || node instanceof HelmRepositoryNode) { + refreshSourcesTreeView(); + if (currentClusterInfo.result.isAzure) { + refreshWorkloadsTreeView(); + } + } else { + refreshWorkloadsTreeView(); + } +} diff --git a/src/commands/setClusterProvider.ts b/src/commands/setClusterProvider.ts index 75a12ecb..a0a924a8 100644 --- a/src/commands/setClusterProvider.ts +++ b/src/commands/setClusterProvider.ts @@ -1,28 +1,23 @@ import { window } from 'vscode'; +import { globalState } from '../extension'; +import { ClusterMetadata } from '../globalState'; +import { KnownClusterProviders, knownClusterProviders } from '../kubernetes/types/kubernetesTypes'; +import { ClusterContextNode } from '../views/nodes/clusterContextNode'; +import { refreshAllTreeViews } from '../views/treeViews'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { ClusterMetadata } from 'data/globalState'; -import { globalState } from 'extension'; -import { KnownClusterProviders, knownClusterProviders } from 'types/kubernetes/clusterProvider'; -import { ClusterNode } from 'ui/treeviews/nodes/cluster/clusterNode'; -import { reloadClustersTreeView } from 'ui/treeviews/treeViews'; -import { refreshAllTreeViews } from './refreshTreeViews'; - -export async function setClusterProvider(clusterNode: ClusterNode) { +export async function setClusterProvider(clusterNode: ClusterContextNode) { const automatically = 'Automatically (Let the extension infer)'; const quickPickItems: string[] = [...knownClusterProviders, automatically]; const pickedProvider = await window.showQuickPick(quickPickItems, { - title: `Choose cluster provider for "${clusterNode.context.cluster}" cluster.`, + title: `Choose cluster provider for "${clusterNode.clusterName}" cluster.`, }); if (!pickedProvider) { return; } - - const clusterOrContextName = clusterNode.cluster?.name || clusterNode.context.name; - const clusterMetadata: ClusterMetadata = globalState.getClusterMetadata(clusterOrContextName) || {}; + const clusterMetadata: ClusterMetadata = globalState.getClusterMetadata(clusterNode.clusterName) || {}; const oldClusterProvider = clusterMetadata.clusterProvider; if (pickedProvider === automatically) { @@ -31,13 +26,9 @@ export async function setClusterProvider(clusterNode: ClusterNode) { clusterMetadata.clusterProvider = pickedProvider as KnownClusterProviders; } - globalState.setClusterMetadata(clusterOrContextName, clusterMetadata); + globalState.setClusterMetadata(clusterNode.clusterName, clusterMetadata); if (clusterMetadata.clusterProvider !== oldClusterProvider) { - if(clusterNode.context.name === kubeConfig.getCurrentContext()) { - refreshAllTreeViews(); - } else { - reloadClustersTreeView(); - } + refreshAllTreeViews(); } } diff --git a/src/commands/setContextToGops.ts b/src/commands/setContextToGops.ts deleted file mode 100644 index bf6f5f1a..00000000 --- a/src/commands/setContextToGops.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Context } from '@kubernetes/client-node'; -import { kubeConfig, setCurrentContext, syncKubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { GitOpsCluster } from 'types/flux/gitOpsCluster'; -import { AnyResourceNode } from 'ui/treeviews/nodes/anyResourceNode'; -import { window } from 'vscode'; - -export async function setContextToGitopsCluster(gopsNode: AnyResourceNode) { - const resource = gopsNode.resource as GitOpsCluster; - const clusterName = resource.metadata.name; - - let matchingContext: Context | undefined; - kubeConfig.getContexts().forEach(context => { - if (context.cluster === clusterName) { - matchingContext = context; - window.showInformationMessage(`Found cluster name matching '${clusterName}'`); - - } - }); - - if(!matchingContext) { - kubeConfig.getContexts().forEach(context => { - if (context.name === clusterName) { - matchingContext = context; - window.showInformationMessage(`Found context name matching '${clusterName}'`); - - } - }); - } - - if(!matchingContext) { - window.showWarningMessage(`Could not find context name or cluster name matching '${clusterName}'`); - return; - } - - const setContextResult = await setCurrentContext(matchingContext.name); - if (setContextResult?.isChanged) { - await syncKubeConfig(); - } -} - diff --git a/src/commands/setCurrentKubernetesContext.ts b/src/commands/setCurrentKubernetesContext.ts index 311bad0b..433fef64 100644 --- a/src/commands/setCurrentKubernetesContext.ts +++ b/src/commands/setCurrentKubernetesContext.ts @@ -1,12 +1,13 @@ -import { syncKubeConfig, setCurrentContext } from 'cli/kubernetes/kubernetesConfig'; -import { ClusterNode } from 'ui/treeviews/nodes/cluster/clusterNode'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { ClusterContextNode } from '../views/nodes/clusterContextNode'; +import { refreshAllTreeViews } from '../views/treeViews'; /** * Sets Kubernetes context and refreshes tree views if needed. */ -export async function setCurrentKubernetesContext(clusterContext: ClusterNode): Promise { - const setContextResult = await setCurrentContext(clusterContext.context.name); +export async function setCurrentKubernetesContext(clusterContext: ClusterContextNode): Promise { + const setContextResult = await kubernetesTools.setCurrentContext(clusterContext.contextName); if (setContextResult?.isChanged) { - await syncKubeConfig(); + refreshAllTreeViews(); } } diff --git a/src/commands/showGlobalState.ts b/src/commands/showGlobalState.ts index 720a24c6..54dbcdc3 100644 --- a/src/commands/showGlobalState.ts +++ b/src/commands/showGlobalState.ts @@ -1,4 +1,4 @@ -import { globalState } from 'extension'; +import { globalState } from '../extension'; export function showGlobalState() { globalState.showGlobalStateValue(); diff --git a/src/commands/showInstalledVersions.ts b/src/commands/showInstalledVersions.ts index d127f0cc..5f55926a 100644 --- a/src/commands/showInstalledVersions.ts +++ b/src/commands/showInstalledVersions.ts @@ -1,9 +1,8 @@ import os from 'os'; import { env, extensions, version, window } from 'vscode'; - -import { getAzureVersion, getFluxVersion, getGitVersion, getKubectlVersion } from 'cli/checkVersions'; -import { failed } from 'types/errorable'; -import { GitOpsExtensionConstants } from 'types/extensionIds'; +import { failed } from '../errorable'; +import { GitOpsExtensionConstants } from '../extension'; +import { getAzureVersion, getFluxVersion, getGitVersion, getKubectlVersion } from '../install'; /** * Show all installed cli versions. diff --git a/src/commands/showLogs.ts b/src/commands/showLogs.ts index 5fc514f6..42bb199f 100644 --- a/src/commands/showLogs.ts +++ b/src/commands/showLogs.ts @@ -1,17 +1,26 @@ -import { ConfigurationTarget, commands, window, workspace } from 'vscode'; - -import { getPodsOfADeployment } from 'cli/kubernetes/kubectlGet'; -import { ResourceNode, podResourceKind } from 'types/showLogsTypes'; -import { ClusterDeploymentNode } from 'ui/treeviews/nodes/cluster/clusterDeploymentNode'; -import { getResourceUri } from 'utils/getResourceUri'; +import { V1ObjectMeta } from '@kubernetes/client-node'; +import { commands, Uri, window } from 'vscode'; +import { allKinds, ResourceKind } from '../kuberesources'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { ClusterDeploymentNode } from '../views/nodes/clusterDeploymentNode'; + +interface ResourceNode { + readonly nodeType: 'resource'; + readonly name?: string; + readonly namespace?: string; + readonly kindName: string; + readonly metadata: V1ObjectMeta; + readonly kind: ResourceKind; + uri(outputFormat: string): Uri; +} /** * Show logs in the editor webview (running Kubernetes extension command) */ export async function showLogs(deploymentNode: ClusterDeploymentNode): Promise { - const pods = await getPodsOfADeployment(deploymentNode.resource.metadata.name, deploymentNode.resource.metadata.namespace); - const pod = pods[0]; + const pods = await kubernetesTools.getPodsOfADeployment(deploymentNode.resource.metadata.name, deploymentNode.resource.metadata.namespace); + const pod = pods?.items[0]; if (!pod) { window.showErrorMessage(`No pods were found from ${deploymentNode.resource.metadata.name} deployment.`); @@ -24,39 +33,11 @@ export async function showLogs(deploymentNode: ClusterDeploymentNode): Promise { - if (!result) { - return; - } - - if (result === 'Never Show Again') { - workspace.getConfiguration('gitops').update('ignoreConfigRecommendations', true, ConfigurationTarget.Global); - return; - } - - vscKubeConfig.update('autorun', true, ConfigurationTarget.Global); - vscKubeConfig.update('follow', true, ConfigurationTarget.Global); - }); } diff --git a/src/commands/showNewUserGuide.ts b/src/commands/showNewUserGuide.ts index c33c353e..7d32622f 100644 --- a/src/commands/showNewUserGuide.ts +++ b/src/commands/showNewUserGuide.ts @@ -1,8 +1,7 @@ import { readFileSync } from 'fs'; -import { tim } from 'tinytim'; import * as vscode from 'vscode'; - -import { asAbsolutePath } from 'utils/asAbsolutePath'; +import { tim } from 'tinytim'; +import { asAbsolutePath } from '../extensionContext'; export function showNewUserGuide() { diff --git a/src/commands/suspend.ts b/src/commands/suspend.ts index 922666e4..3f31f5f2 100644 --- a/src/commands/suspend.ts +++ b/src/commands/suspend.ts @@ -1,18 +1,57 @@ - -import { kubectlPatchNamespacedResource } from 'cli/kubernetes/kubernetesToolsKubectl'; -import { GitRepositoryNode } from 'ui/treeviews/nodes/source/gitRepositoryNode'; -import { HelmRepositoryNode } from 'ui/treeviews/nodes/source/helmRepositoryNode'; -import { GitOpsSetNode } from 'ui/treeviews/nodes/wge/gitOpsSetNode'; -import { HelmReleaseNode } from 'ui/treeviews/nodes/workload/helmReleaseNode'; -import { KustomizationNode } from 'ui/treeviews/nodes/workload/kustomizationNode'; +import { window } from 'vscode'; +import { AzureClusterProvider, azureTools, isAzureProvider } from '../azure/azureTools'; +import { failed } from '../errorable'; +import { fluxTools } from '../flux/fluxTools'; +import { FluxSource, FluxWorkload } from '../flux/fluxTypes'; +import { GitRepositoryNode } from '../views/nodes/gitRepositoryNode'; +import { HelmReleaseNode } from '../views/nodes/helmReleaseNode'; +import { HelmRepositoryNode } from '../views/nodes/helmRepositoryNode'; +import { KustomizationNode } from '../views/nodes/kustomizationNode'; +import { OCIRepositoryNode } from '../views/nodes/ociRepositoryNode'; +import { getCurrentClusterInfo, refreshSourcesTreeView, refreshWorkloadsTreeView } from '../views/treeViews'; /** * Suspend source or workload reconciliation and refresh its Tree View. * * @param node sources tree view node */ -export async function suspend(node: GitRepositoryNode | HelmReleaseNode | KustomizationNode | HelmRepositoryNode | GitOpsSetNode) { - await kubectlPatchNamespacedResource(node.resource, '{"spec": {"suspend": true}}'); +export async function suspend(node: GitRepositoryNode | HelmReleaseNode | KustomizationNode | HelmRepositoryNode) { + + const currentClusterInfo = await getCurrentClusterInfo(); + if (failed(currentClusterInfo)) { + return; + } + + const fluxResourceType: FluxSource | FluxWorkload | 'unknown' = node instanceof GitRepositoryNode ? + 'source git' : node instanceof HelmRepositoryNode ? + 'source helm' : node instanceof OCIRepositoryNode ? + 'source oci' : node instanceof HelmReleaseNode ? + 'helmrelease' : node instanceof KustomizationNode ? + 'kustomization' : 'unknown'; + + if (fluxResourceType === 'unknown') { + window.showErrorMessage(`Unknown object kind ${fluxResourceType}`); + return; + } + + if (currentClusterInfo.result.isAzure) { + // TODO: implement + if (fluxResourceType === 'helmrelease' || fluxResourceType === 'kustomization') { + window.showInformationMessage('Not implemented on AKS/ARC', { modal: true }); + return; + } + + await azureTools.suspend(node.resource.metadata.name || '', currentClusterInfo.result.contextName, currentClusterInfo.result.clusterProvider as AzureClusterProvider); + } else { + await fluxTools.suspend(fluxResourceType, node.resource.metadata.name || '', node.resource.metadata.namespace || ''); + } - node.dataProvider.reload(); + if (node instanceof GitRepositoryNode || node instanceof OCIRepositoryNode || node instanceof HelmRepositoryNode) { + refreshSourcesTreeView(); + if (currentClusterInfo.result.isAzure) { + refreshWorkloadsTreeView(); + } + } else { + refreshWorkloadsTreeView(); + } } diff --git a/src/commands/trace.ts b/src/commands/trace.ts index f0e3ce9c..bc6de419 100644 --- a/src/commands/trace.ts +++ b/src/commands/trace.ts @@ -1,20 +1,18 @@ import { window } from 'vscode'; - -import { fluxTools } from 'cli/flux/fluxTools'; -import { getResource } from 'cli/kubernetes/kubectlGet'; -import { telemetry } from 'extension'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { AnyResourceNode } from 'ui/treeviews/nodes/anyResourceNode'; -import { WorkloadNode } from 'ui/treeviews/nodes/workload/workloadNode'; +import { telemetry } from '../extension'; +import { fluxTools } from '../flux/fluxTools'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { AnyResourceNode } from '../views/nodes/anyResourceNode'; +import { WorkloadNode } from '../views/nodes/workloadNode'; /** * Run flux trace for the Workloads tree view node. */ export async function trace(node: AnyResourceNode | WorkloadNode) { - const resourceName = node.resource.metadata.name; - const resourceNamespace = node.resource.metadata.namespace || 'flux-system'; - const resourceKind = node.resource.kind; - let resourceApiVersion = node.resource.apiVersion; + const resourceName = node.resource.metadata?.name || ''; + const resourceNamespace = node.resource.metadata?.namespace || 'flux-system'; + const resourceKind = node.resource.kind || ''; + let resourceApiVersion = node.resource.apiVersion || ''; if (!resourceName) { window.showErrorMessage('"name" is required to run `flux trace`.'); @@ -29,7 +27,7 @@ export async function trace(node: AnyResourceNode | WorkloadNode) { // flux tree fetched items don't have the "apiVersion" property if (!resourceApiVersion) { - const resource = await getResource(resourceName, resourceNamespace, resourceKind as Kind); + const resource = await kubernetesTools.getResource(resourceName, resourceNamespace, resourceKind); const apiVersion = resource?.apiVersion; if (!apiVersion && !apiVersion) { window.showErrorMessage('"apiVersion" is required to run `flux trace`'); diff --git a/src/data/contextData.ts b/src/data/contextData.ts deleted file mode 100644 index d6ad7563..00000000 --- a/src/data/contextData.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { getResource } from 'cli/kubernetes/kubectlGet'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { HelmRelease } from 'types/flux/helmRelease'; -import { ConfigMap, Kind } from 'types/kubernetes/kubernetesTypes'; -import { NamespaceNode } from 'ui/treeviews/nodes/namespaceNode'; -import { TreeNode } from 'ui/treeviews/nodes/treeNode'; -import { WgeContainerNode } from 'ui/treeviews/nodes/wge/wgeNodes'; -import { TreeItemCollapsibleState } from 'vscode'; -import { ApiState, KindApiParams } from '../cli/kubernetes/apiResources'; - -// a data store for each context defined in kubeconfig. -// view data is stored here. -// allows to safely switch contexts without laggy queries from previous context overwriting data in the global tree view -export class ContextData { - public viewData: { [key: string]: ViewData; }; - public contextName = ''; - public apiState = ApiState.Loading; - // Current cluster supported kubernetes resource kinds. - public apiResources: Map | undefined; - - public portalUrl?: string; - public wgeClusterName?: string; - - constructor(contextName: string) { - this.contextName = contextName; - this.viewData = { - 'source': new ViewData(), - 'workload': new ViewData(), - 'wge': new ViewData(), - }; - } - -} - -const contextDatas = new Map(); - -export function currentContextData() { - let currentData = contextDatas.get(kubeConfig.currentContext); - if (!currentData) { - currentData = new ContextData(kubeConfig.currentContext); - contextDatas.set(kubeConfig.currentContext, currentData); - } - return currentData; -} - -export class ViewData { - public nodes: TreeNode[] = []; - public collapsibleStates = new Map(); - public loading = false; - - - get savedNodes() { - let nodes: TreeNode[] = []; - this.nodes.forEach(node => { - nodes = nodes.concat(node.children); - }); - return nodes.concat(this.nodes); - } - - saveCollapsibleStates() { - this.collapsibleStates.clear(); - - for (const node of this.savedNodes) { - const key = node.viewStateKey; - if (key) { - this.collapsibleStates.set(key, node.collapsibleState || TreeItemCollapsibleState.Collapsed); - } - } - } - - loadCollapsibleStates() { - for (const node of this.savedNodes) { - const key = node.viewStateKey; - if (key) { - const state = this.collapsibleStates.get(key); - if (state) { - node.collapsibleState = state; - if(node instanceof NamespaceNode) { - const withIcons = !node.parent || node.parent instanceof WgeContainerNode; - node.updateLabel(withIcons); - } - } - } - } - } -} - -export async function loadContextData() { - const context = currentContextData(); - const config = await getResource('weave-gitops-interop', 'flux-system', Kind.ConfigMap) as ConfigMap; - - if(config) { - context.portalUrl = config.data.portalUrl; - context.wgeClusterName = config.data.wgeClusterName; - } - - context.portalUrl ??= await wgeHelmReleasePortalUrl(); - context.wgeClusterName ??= kubeConfig.getCurrentCluster()?.name || kubeConfig.currentContext; -} - -async function wgeHelmReleasePortalUrl() { - const wgeHelmRelease = await getResource('weave-gitops-enterprise', 'flux-system', Kind.HelmRelease); - if(!wgeHelmRelease) { - return; - } - - const values = wgeHelmRelease.spec?.values as any; - const hosts = values?.ingress?.hosts; - const host = hosts?.[0]; - - if(host) { - return `https://${host.host}`; - } -} diff --git a/src/data/telemetry.ts b/src/data/telemetry.ts deleted file mode 100644 index 909db5fd..00000000 --- a/src/data/telemetry.ts +++ /dev/null @@ -1,100 +0,0 @@ -import TelemetryReporter from '@vscode/extension-telemetry'; -import { env, ExtensionContext, ExtensionMode } from 'vscode'; - -import { TelemetryErrorEvent, TelemetryEvent } from 'types/telemetryEventNames'; - -/** - * Map event names with the data type of payload sent - * When undefined - send only the event name. - */ -interface TelemetryEventNamePropertyMapping { - [TelemetryEvent.Startup]: undefined; - [TelemetryEvent.EnableGitOps]: { - clusterProvider: string; - }; - [TelemetryEvent.DisableGitOps]: { - clusterProvider: string; - }; - [TelemetryEvent.NewInstall]: undefined; - [TelemetryEvent.CreateSourceOpenWebview]: undefined; - [TelemetryEvent.CreateSource]: { - kind: string; - }; - [TelemetryEvent.DeleteSource]: { - kind: string; - }; - [TelemetryEvent.CreateWorkload]: { - kind: string; - }; - [TelemetryEvent.DeleteWorkload]: { - kind: string; - }; -} - -export class Telemetry { - - private context: ExtensionContext; - private reporter: TelemetryReporter; - - constructor(context: ExtensionContext, extensionVersion: string, extensionId: string) { - this.context = context; - const key = '9a491deb-120a-4a6e-8893-f528d4f6bd9c'; - this.reporter = new TelemetryReporter(extensionId, extensionVersion, key); - context.subscriptions.push(this.reporter); - } - - /** - * Check if it's allowed to send the telemetry. - */ - private canSend(): boolean { - // Don't send telemetry when developing or testing the extension - if (this.context.extensionMode !== ExtensionMode.Production) { - return false; - } - // Don't send telemetry when user disabled it in Settings - if (!env.isTelemetryEnabled) { - return false; - } - return true; - } - - /** - * Send custom events. - * - * @param eventName sent message title - * @param payload custom properties to add to the message - */ - send(eventName: E, payload?: T[E]): void { - if (!this.canSend()) { - return; - } - - // @ts-ignore - this.reporter.sendTelemetryEvent(eventName, payload); - } - - /** - * Send caught or uncaught errors. - * - * @param eventName sent message title - * @param error error object of the uncaught exception - */ - sendError(eventName: TelemetryErrorEvent, error?: Error): void { - if (!this.canSend()) { - return; - } - - if (!error) { - error = new Error(eventName); - } - - this.reporter.sendTelemetryException(error, { - name: eventName, - }); - - } - - dispose(): void { - this.reporter.dispose(); - } -} diff --git a/src/types/errorable.ts b/src/errorable.ts similarity index 51% rename from src/types/errorable.ts rename to src/errorable.ts index afab1391..9379c33c 100644 --- a/src/types/errorable.ts +++ b/src/errorable.ts @@ -18,19 +18,3 @@ export function failed(e: Errorable): e is Failed { return !e.succeeded; } -export function result(e: Errorable): T | undefined { - if (succeeded(e)) { - return e.result; - } else { - return undefined; - } -} - -export async function aresult(e: Promise>): Promise { - const r = await e; - return result(r); -} - -export function results(es: Errorable[]): (T | undefined)[] { - return es.map(e => result(e)); -} diff --git a/src/extension.ts b/src/extension.ts index b3492fd2..3d598c44 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,36 +1,30 @@ import { commands, ExtensionContext, ExtensionMode, window, workspace } from 'vscode'; - -import { kubeProxyKeepAlive, stopKubeProxy } from 'cli/kubernetes/kubectlProxy'; -import { syncKubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { initKubeConfigWatcher } from 'cli/kubernetes/kubernetesConfigWatcher'; -import { checkWGEVersion } from './cli/checkVersions'; -import * as shell from './cli/shell/exec'; -import { registerCommands } from './commands/commands'; +import { CommandId, registerCommands } from './commands'; import { getExtensionVersion } from './commands/showInstalledVersions'; import { showNewUserGuide } from './commands/showNewUserGuide'; -import { GlobalState, GlobalStateKey } from './data/globalState'; -import { Telemetry } from './data/telemetry'; -import { CommandId, ContextId, GitOpsExtensionConstants } from './types/extensionIds'; -import { TelemetryEvent } from './types/telemetryEventNames'; -import { checkInstalledFluxVersion } from './ui/promptToInstallFlux'; -import { statusBar } from './ui/statusBar'; -import { clusterDataProvider, createTreeViews, sourceDataProvider, workloadDataProvider } from './ui/treeviews/treeViews'; +import { ContextTypes, setVSCodeContext } from './vscodeContext'; +import { succeeded } from './errorable'; +import { setExtensionContext } from './extensionContext'; +import { GlobalState, GlobalStateKey } from './globalState'; +import { checkFluxPrerequisites, checkWGEVersion, promptToInstallFlux } from './install'; +import { statusBar } from './statusBar'; +import { Telemetry, TelemetryEventNames } from './telemetry'; +import { createTreeViews, clusterTreeViewProvider, sourceTreeViewProvider, workloadTreeViewProvider } from './views/treeViews'; +import { shell } from './shell'; /** Disable interactive modal dialogs, useful for testing */ -export let skipConfirmations = false; +export let disableConfirmations = false; export let experimentalFlag = false; -/* - * This is the extension runtime context. contains workspace state, subscriptions, paths, persistent state, etc. - Should not be confused with vscode context (like 'gitops:noClusterSelected' that's used in package.json to specify when to show/hide commands) - */ -export let extensionContext: ExtensionContext; + +export const enum GitOpsExtensionConstants { + ExtensionId = 'weaveworks.vscode-gitops-tools', +} /** State that is saved even between editor reloads */ export let globalState: GlobalState; /** Methods to report telemetry over Application Insights (Exceptions or Custom Events). */ export let telemetry: Telemetry | any; -export let isActive = true; /** * Called when GitOps extension is activated. @@ -38,30 +32,31 @@ export let isActive = true; */ export async function activate(context: ExtensionContext) { // Keep a reference to the extension context - extensionContext = context; - listenExtensionConfigChanged(); + setExtensionContext(context); + listenConfigChanged(); globalState = new GlobalState(context); telemetry = new Telemetry(context, getExtensionVersion(), GitOpsExtensionConstants.ExtensionId); - initData(); + // create gitops tree views + createTreeViews(); // register gitops commands registerCommands(context); - telemetry.send(TelemetryEvent.Startup); + telemetry.send(TelemetryEventNames.Startup); if (globalState.get(GlobalStateKey.FirstEverActivationStorageKey) === undefined) { - telemetry.send(TelemetryEvent.NewInstall); + telemetry.send(TelemetryEventNames.NewInstall); showNewUserGuide(); globalState.set(GlobalStateKey.FirstEverActivationStorageKey, false); } // set vscode context: developing extension. test is also dev - setVSCodeContext(ContextId.IsDev, context.extensionMode === ExtensionMode.Development || context.extensionMode === ExtensionMode.Test ); + setVSCodeContext(ContextTypes.IsDev, context.extensionMode === ExtensionMode.Development || context.extensionMode === ExtensionMode.Test ); if(context.extensionMode === ExtensionMode.Test) { - skipConfirmations = true; + disableConfirmations = true; } @@ -71,40 +66,34 @@ export async function activate(context: ExtensionContext) { } - // check version and show 'Install Flux?' dialog if flux is not installed - checkInstalledFluxVersion(); + // show error notification if flux is not installed + const fluxFoundResult = await promptToInstallFlux(); + if (succeeded(fluxFoundResult)) { + // check flux prerequisites + await checkFluxPrerequisites(); + } checkWGEVersion(); let api = { shell: shell, data: { - clusterTreeViewProvider: clusterDataProvider, - sourceTreeViewProvider: sourceDataProvider, - workloadTreeViewProvider: workloadDataProvider, + clusterTreeViewProvider: clusterTreeViewProvider, + sourceTreeViewProvider: sourceTreeViewProvider, + workloadTreeViewProvider: workloadTreeViewProvider, }}; return api; } -async function initData() { - syncKubeConfig(true); - initKubeConfigWatcher(); - kubeProxyKeepAlive(); - - // wait for kubectl proxy to start for faster initial tree view loading - // setTimeout(() => { - createTreeViews(); - // }, 200); -} - -function listenExtensionConfigChanged() { +function listenConfigChanged() { workspace.onDidChangeConfiguration(async e => { - if(!e.affectsConfiguration('gitops.weaveGitopsEnterprise')) { + if(!e.affectsConfiguration('gitops')) { return; } const selected = await window.showInformationMessage('Configuration changed. Reload VS Code to apply?', 'Reload'); + console.log(e); if(selected === 'Reload') { await commands.executeCommand(CommandId.VSCodeReload); } @@ -115,28 +104,11 @@ export function enabledWGE(): boolean { return workspace.getConfiguration('gitops').get('weaveGitopsEnterprise') || false; } -export function enabledFluxChecks(): boolean { - return workspace.getConfiguration('gitops').get('doFluxCheck') || false; - -} - -export function suppressDebugMessages(): boolean { - return workspace.getConfiguration('gitops').get('suppressDebugMessages') || false; -} - /** * Called when extension is deactivated. */ export function deactivate() { - isActive = false; telemetry?.dispose(); statusBar?.dispose(); - stopKubeProxy(); -} - - - -export async function setVSCodeContext(context: ContextId, value: boolean) { - return await commands.executeCommand(CommandId.VSCodeSetContext, context, value); } diff --git a/src/extensionContext.ts b/src/extensionContext.ts new file mode 100644 index 00000000..02f00b7f --- /dev/null +++ b/src/extensionContext.ts @@ -0,0 +1,27 @@ +import { ExtensionContext, Uri } from 'vscode'; + +let extensionContext: ExtensionContext; + +/** + * Save a referece for this (GitOps) extension's context + */ +export function setExtensionContext(context: ExtensionContext) { + extensionContext = context; +} + +/** + * Return a reference for this (GitOps) extension's context + */ +export function getExtensionContext(): ExtensionContext { + return extensionContext; +} + +/** + * Transform relative path inside the extension folder + * to absolute path. + * @param relativePath relative path to the file + * @returns Uri of the file + */ +export function asAbsolutePath(relativePath: string): Uri { + return Uri.file(extensionContext.asAbsolutePath(relativePath)); +} diff --git a/src/extensionState.ts b/src/extensionState.ts new file mode 100644 index 00000000..d659d996 --- /dev/null +++ b/src/extensionState.ts @@ -0,0 +1,29 @@ +interface ExtensionStateMap { + fluxVersion: string; +} + +type ExtensionStateKey = keyof ExtensionStateMap; + +class ExtensionState { + /** + * All the items of the global state. + */ + private state: ExtensionStateMap = { + fluxVersion: 'Not installed', + }; + + + get(stateKey: T): ExtensionStateMap[T] { + return this.state[stateKey]; + } + + set(stateKey: T, newValue: ExtensionStateMap[T]): void { + this.state[stateKey] = newValue; + } +} + +/** + * Global state (temporary, while extension is running). + */ +export const extensionState = new ExtensionState(); + diff --git a/src/types/fileTypes.ts b/src/fileTypes.ts similarity index 100% rename from src/types/fileTypes.ts rename to src/fileTypes.ts diff --git a/src/cli/flux/cliArgs.ts b/src/flux/cliArgs.ts similarity index 100% rename from src/cli/flux/cliArgs.ts rename to src/flux/cliArgs.ts diff --git a/src/cli/flux/fluxTools.ts b/src/flux/fluxTools.ts similarity index 80% rename from src/cli/flux/fluxTools.ts rename to src/flux/fluxTools.ts index 94860a1b..f22e8559 100644 --- a/src/cli/flux/fluxTools.ts +++ b/src/flux/fluxTools.ts @@ -1,11 +1,11 @@ -import safesh from 'shell-escape-tag'; import { window } from 'vscode'; - -import * as shell from 'cli/shell/exec'; -import { enabledFluxChecks, telemetry } from 'extension'; -import { FluxSource, FluxTreeResources, FluxWorkload } from 'types/fluxCliTypes'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { parseJson } from 'utils/jsonUtils'; +import safesh from 'shell-escape-tag'; +import { telemetry } from '../extension'; +import { KubernetesObjectKinds, SourceObjectKinds } from '../kubernetes/types/kubernetesTypes'; +import { shell } from '../shell'; +import { TelemetryErrorEventNames } from '../telemetry'; +import { parseJson } from '../utils/jsonUtils'; +import { FluxSource, FluxTreeResources, FluxWorkload } from './fluxTypes'; import { buildCLIArgs, cliKind } from './cliArgs'; /** @@ -64,16 +64,13 @@ class FluxTools { * https://github.com/fluxcd/flux2/blob/main/cmd/flux/check.go */ async check(context: string): Promise<{ prerequisites: FluxPrerequisite[]; controllers: FluxController[]; } | undefined> { - if (!enabledFluxChecks()) { - return undefined; - } const result = await shell.execWithOutput(safesh`flux check --context ${context}`, { revealOutputView: false }); if (result.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_CHECK); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_CHECK); const stderr = result?.stderr; if (stderr) { - window.showWarningMessage(String(result?.stderr || '')); + window.showErrorMessage(String(result?.stderr || '')); } return undefined; } @@ -133,17 +130,11 @@ class FluxTools { */ async tree(name: string, namespace: string): Promise { - const cmd = `flux tree kustomization ${name} -n ${namespace} -o json`; - const treeShellResult = await shell.exec(cmd); + const treeShellResult = await shell.exec(`flux tree kustomization ${name} -n ${namespace} -o json`); if (treeShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_TREE); - let errorData = treeShellResult.stderr; - if (treeShellResult.code === null) { - errorData += `Command '${cmd}' timed out`; - } - // + (treeShellResult.code === null ? 'Command timed out' : ''; - window.showWarningMessage(`Failed to get resources created by the kustomization ${name}. ERROR: ${errorData}`); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_TREE); + window.showErrorMessage(`Failed to get resources created by the workload ${name}. ERROR: ${treeShellResult?.stderr}`); return; } @@ -163,7 +154,7 @@ class FluxTools { } const installShellResult = await shell.execWithOutput(`flux install ${contextArg}`); if (installShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_INSTALL); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_INSTALL); } } @@ -180,7 +171,7 @@ class FluxTools { } const uninstallShellResult = await shell.execWithOutput(`flux uninstall --silent ${contextArg}`); if (uninstallShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_UNINSTALL); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_UNINSTALL); } } @@ -195,7 +186,7 @@ class FluxTools { async suspend(type: FluxSource | FluxWorkload, name: string, namespace: string) { const suspendShellResult = await shell.execWithOutput(`flux suspend ${type} ${name} -n ${namespace}`); if (suspendShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_SUSPEND); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_SUSPEND); } } @@ -210,7 +201,7 @@ class FluxTools { async resume(type: FluxSource | FluxWorkload, name: string, namespace: string) { const resumeShellResult = await shell.execWithOutput(`flux resume ${type} ${name} -n ${namespace}`); if (resumeShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_RESUME); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_RESUME); } } @@ -222,11 +213,10 @@ class FluxTools { * @param name resource name * @param namespace resource namespace */ - async reconcile(type: FluxSource | FluxWorkload, name: string, namespace: string, withSource = false) { - const withSourceArg = withSource ? '--with-source' : ''; - const reconcileShellResult = await shell.execWithOutput(`flux reconcile ${type} ${name} -n ${namespace} ${withSourceArg}`); + async reconcile(type: FluxSource | FluxWorkload, name: string, namespace: string) { + const reconcileShellResult = await shell.execWithOutput(`flux reconcile ${type} ${name} -n ${namespace}`); if (reconcileShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_RECONCILE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_RECONCILE); } } @@ -236,7 +226,7 @@ class FluxTools { async trace(name: string, kind: string, apiVersion: string, namespace: string) { const traceShellResult = await shell.execWithOutput(`flux trace ${name} --kind=${kind} --api-version=${apiVersion} --namespace=${namespace}`); if (traceShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_TRACE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_TRACE); } } @@ -251,7 +241,7 @@ class FluxTools { async delete(type: FluxSource | FluxWorkload, name: string, namespace: string) { const deleteSourceShellResult = await shell.execWithOutput(`flux delete ${type} ${name} -n ${namespace} --silent`); if (deleteSourceShellResult.code !== 0) { - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_DELETE_SOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_DELETE_SOURCE); } } @@ -284,7 +274,7 @@ class FluxTools { if (shellResult.code !== 0) { window.showErrorMessage(shellResult.stderr); - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_CREATE_SOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_CREATE_SOURCE); } const output = shellResult.stdout || shellResult.stderr; @@ -299,7 +289,7 @@ class FluxTools { if(shellResult.code !== 0) { window.showErrorMessage(shellResult.stderr); - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_CREATE_SOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_CREATE_SOURCE); return '---'; } @@ -313,7 +303,7 @@ class FluxTools { if (shellResult.code !== 0) { window.showErrorMessage(shellResult.stderr); - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_CREATE_KUSTOMIZATION); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_CREATE_KUSTOMIZATION); } } @@ -323,7 +313,7 @@ class FluxTools { if(shellResult.code !== 0) { window.showErrorMessage(shellResult.stderr); - telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_CREATE_KUSTOMIZATION); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_RUN_FLUX_CREATE_KUSTOMIZATION); return '---'; } diff --git a/src/types/fluxCliTypes.ts b/src/flux/fluxTypes.ts similarity index 100% rename from src/types/fluxCliTypes.ts rename to src/flux/fluxTypes.ts diff --git a/src/cli/flux/fluxUtils.ts b/src/flux/fluxUtils.ts similarity index 95% rename from src/cli/flux/fluxUtils.ts rename to src/flux/fluxUtils.ts index 6abd3e12..e4bdfe07 100644 --- a/src/cli/flux/fluxUtils.ts +++ b/src/flux/fluxUtils.ts @@ -1,4 +1,4 @@ -import { sanitizeRFC1123 } from 'utils/stringUtils'; +import { sanitizeRFC1123 } from '../utils/stringUtils'; /** RegEx to validate the resource name (except the length of the string) */ const RFC1123SubdomainRegex = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/; diff --git a/src/cli/git/gitInfo.ts b/src/git/gitInfo.ts similarity index 88% rename from src/cli/git/gitInfo.ts rename to src/git/gitInfo.ts index 492d324a..bc5b2a7c 100644 --- a/src/cli/git/gitInfo.ts +++ b/src/git/gitInfo.ts @@ -1,12 +1,12 @@ -import gitUrlParse from 'git-url-parse'; import path from 'path'; -import { window } from 'vscode'; -import { checkGitVersion } from 'cli/checkVersions'; -import * as shell from 'cli/shell/exec'; -import { makeSSHUrlFromGitUrl } from 'commands/createSource'; -import { GitRepository } from 'types/flux/gitRepository'; -import { getGitRepositories } from 'cli/kubernetes/kubectlGet'; +import gitUrlParse from 'git-url-parse'; +import { workspace, window } from 'vscode'; +import { makeSSHUrlFromGitUrl } from '../commands/createSource'; +import { checkGitVersion } from '../install'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { GitRepository } from '../kubernetes/types/flux/gitRepository'; +import { shell } from '../shell'; export interface GitInfo { @@ -25,8 +25,8 @@ export async function getGitRepositoryforGitInfo(gitInfo?: GitInfo): Promise gr.spec.url === gitInfo.url); + const gitRepositories = await kubernetesTools.getGitRepositories(); + return gitRepositories?.items.find(gr => gr.spec.url === gitInfo.url); } /** diff --git a/src/data/globalState.ts b/src/globalState.ts similarity index 96% rename from src/data/globalState.ts rename to src/globalState.ts index 601ff6d5..77341dc8 100644 --- a/src/data/globalState.ts +++ b/src/globalState.ts @@ -1,6 +1,5 @@ import { ExtensionContext, window, workspace } from 'vscode'; - -import { KnownClusterProviders } from 'types/kubernetes/clusterProvider'; +import { KnownClusterProviders } from './kubernetes/types/kubernetesTypes'; export interface ClusterMetadata { azureResourceGroup?: string; diff --git a/src/index.d.ts b/src/index.d.ts index 97683641..222d0a57 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,4 +1,2 @@ declare module 'tinytim'; declare module 'shell-escape-tag'; -declare module 'lite-deep-equal'; -declare module 'tree-kill'; diff --git a/src/cli/checkVersions.ts b/src/install.ts similarity index 64% rename from src/cli/checkVersions.ts rename to src/install.ts index 93cdd891..728a3082 100644 --- a/src/cli/checkVersions.ts +++ b/src/install.ts @@ -1,12 +1,12 @@ import { commands, Uri, window } from 'vscode'; - -import * as shell from 'cli/shell/exec'; -import { enabledWGE, telemetry } from 'extension'; -import { Errorable, failed } from 'types/errorable'; -import { TelemetryError } from 'types/telemetryEventNames'; -import { clusterDataProvider } from 'ui/treeviews/treeViews'; -import { parseJson } from 'utils/jsonUtils'; -import { shellCodeError } from './shell/exec'; +import { CommandId } from './commands'; +import { installFluxCli } from './commands/installFluxCli'; +import { Errorable, failed } from './errorable'; +import { enabledWGE, telemetry } from './extension'; +import { extensionState } from './extensionState'; +import { shell, shellCodeError } from './shell'; +import { TelemetryErrorEventNames } from './telemetry'; +import { parseJson } from './utils/jsonUtils'; interface KubectlVersion { major: string; @@ -26,8 +26,6 @@ export interface KubectlVersionResult { serverVersion: KubectlVersion; } -export let fluxVersion: string; - export async function getKubectlVersion(): Promise> { const kubectlVersionShellResult = await shell.exec('kubectl version --short -o json'); @@ -72,7 +70,9 @@ export async function getAzureVersion(): Promise> { } } - +interface FluxVersion { + flux: string; +} /** * Return flux version string. * @see https://fluxcd.io/docs/cmd/flux_version/ @@ -81,16 +81,13 @@ export async function getFluxVersion(): Promise> { const fluxVersionShellResult = await shell.exec('flux version --client -o json'); if (fluxVersionShellResult.code === 0) { - fluxVersion = parseJson(fluxVersionShellResult.stdout.trim()).flux; - clusterDataProvider.redrawCurrentNode(); - + const fluxVersion: FluxVersion = parseJson(fluxVersionShellResult.stdout.trim()); + extensionState.set('fluxVersion', fluxVersion.flux); return { succeeded: true, - result: fluxVersion, + result: fluxVersion.flux, }; } else { - - fluxVersion = 'unavailable'; return { succeeded: false, error: [shellCodeError(fluxVersionShellResult)], @@ -98,6 +95,50 @@ export async function getFluxVersion(): Promise> { } } +/** + * Show notification with button to install flux + * (only when flux was not found). + */ +export async function promptToInstallFlux(): Promise> { + const fluxVersion = await getFluxVersion(); + if (failed(fluxVersion)) { + showInstallFluxNotification(); + return { + succeeded: false, + error: ['Flux not found'], + }; + } else { + return { + succeeded: true, + result: null, + }; + } +} + +async function showInstallFluxNotification() { + const installButton = 'Install Flux'; + const pressedButton = await window.showErrorMessage('Please install flux CLI to use GitOps Tools.', installButton); + if (pressedButton === installButton) { + installFluxCli(); + } +} + +/** + * Show warning notification only in case the + * flux prerequisite check has failed. + * @see https://fluxcd.io/docs/cmd/flux_check/ + */ +export async function checkFluxPrerequisites() { + const prerequisiteShellResult = await shell.execWithOutput('flux check --pre', { revealOutputView: false }); + + if (prerequisiteShellResult.code !== 0) { + const showOutput = 'Show Output'; + const showOutputConfirm = await window.showWarningMessage('Flux prerequisites check failed.', showOutput); + if (showOutput === showOutputConfirm) { + commands.executeCommand(CommandId.ShowOutputChannel); + } + } +} /** * Return git version or undefined depending @@ -127,7 +168,7 @@ export async function checkGitVersion(): Promise { const gitVersionShellResult = await getGitVersion(); if (failed(gitVersionShellResult)) { - telemetry.sendError(TelemetryError.GIT_NOT_INSTALLED); + telemetry.sendError(TelemetryErrorEventNames.GIT_NOT_INSTALLED); const installButton = 'Install'; const confirm = await window.showErrorMessage('Please install Git.', installButton); if (confirm === installButton) { diff --git a/src/k8s/client.ts b/src/k8s/client.ts deleted file mode 100644 index 3e004c9e..00000000 --- a/src/k8s/client.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { createInformers, destroyInformers } from './informers'; -import { kubeProxyConfig } from 'cli/kubernetes/kubectlProxy'; - -export let k8sCoreApi: k8s.CoreV1Api | undefined; -export let k8sCustomApi: k8s.CustomObjectsApi | undefined; - -export function createK8sClients() { - destroyK8sClients(); - - if(kubeProxyConfig) { - k8sCoreApi = kubeProxyConfig.makeApiClient(k8s.CoreV1Api); - k8sCustomApi = kubeProxyConfig.makeApiClient(k8s.CustomObjectsApi); - - createInformers(kubeProxyConfig); - } -} - -export function destroyK8sClients() { - destroyInformers(); - - k8sCoreApi = undefined; - k8sCustomApi = undefined; -} - - diff --git a/src/k8s/createKubeProxyConfig.ts b/src/k8s/createKubeProxyConfig.ts deleted file mode 100644 index 01e44213..00000000 --- a/src/k8s/createKubeProxyConfig.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; - - -export function createProxyConfig(port: number) { - const cluster = { - name: kubeConfig.getCurrentCluster()?.name, - server: `http://127.0.0.1:${port}`, - }; - - const user = {...kubeConfig.getCurrentUser()}; - if(user) { - user['exec'] = undefined; - } - - - const context = { - name: kubeConfig.getCurrentContext(), - user: user?.name, - cluster: cluster.name, - }; - - const kc = new k8s.KubeConfig(); - kc.loadFromOptions({ - clusters: [cluster], - users: [user], - contexts: [context], - currentContext: context.name, - }); - return kc; -} - diff --git a/src/k8s/informers.ts b/src/k8s/informers.ts deleted file mode 100644 index b7d6ffef..00000000 --- a/src/k8s/informers.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { getAPIParams } from 'cli/kubernetes/apiResources'; -import { GitRepository } from 'types/flux/gitRepository'; -import { FluxSourceKinds, FluxWorkloadKinds } from 'types/flux/object'; -import { Kind, KubernetesListObject, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { KubernetesObjectDataProvider } from 'ui/treeviews/dataProviders/kubernetesObjectDataProvider'; -import { sourceDataProvider, workloadDataProvider } from 'ui/treeviews/treeViews'; -import { k8sCustomApi } from './client'; - - -let informers: k8s.Informer[] = []; - -export function createInformers(kc: k8s.KubeConfig) { - FluxSourceKinds.forEach(kind => { - createInformer(kc, sourceDataProvider, kind); - }); - - FluxWorkloadKinds.forEach(kind => { - createInformer(kc, workloadDataProvider, kind); - }); -} - -export function destroyInformers() { - informers.forEach(informer => { - informer.stop(); - }); - - informers = []; -} - - -async function createInformer(kc: k8s.KubeConfig, receiver: KubernetesObjectDataProvider, kind: Kind) { - const api = getAPIParams(kind); - if (!api) { - return; - } - - const listFn = async () => { - const result = await k8sCustomApi!.listClusterCustomObject(api.group, api.version, api.plural); - const kbody = result.body as KubernetesListObject; - return Promise.resolve({ response: result.response, body: kbody }); - }; - - const informer = k8s.makeInformer( - kc, - `/apis/${api.group}/${api.version}/${api.plural}`, - listFn, - ); - - try { - await informer.start(); - registerInformerEvents(informer, receiver); - informers.push(informer); - } catch (error) { - destroyInformers(); - } -} - -function registerInformerEvents(informer: k8s.Informer, receiver: KubernetesObjectDataProvider) { - informer?.on('add', (obj: KubernetesObject) => { - - receiver.add(obj); - }); - - informer?.on('update', (obj: KubernetesObject) => { - receiver.update(obj); - }); - - informer?.on('delete', (obj: KubernetesObject) => { - receiver.delete(obj); - }); - - informer?.on('error', (err: Error) => { - destroyInformers(); - }); -} diff --git a/src/k8s/list.ts b/src/k8s/list.ts deleted file mode 100644 index a82e11ee..00000000 --- a/src/k8s/list.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { getAPIParams } from 'cli/kubernetes/apiResources'; -import { ToolkitObject } from 'types/flux/object'; -import { Kind, KubernetesListObject, Namespace } from 'types/kubernetes/kubernetesTypes'; -import { k8sCoreApi, k8sCustomApi } from './client'; - -export async function k8sList(kind: Kind): Promise { - const api = getAPIParams(kind); - if(!api) { - return; - } - - if(!k8sCustomApi) { - return; - } - - try { - const result = await k8sCustomApi.listClusterCustomObject(api.group, api.version, api.plural); - const kbody = result.body as KubernetesListObject; - return kbody.items; - } catch (error) { - return; - } -} - - -export async function k8sGet(name: string, namespace: string, kind: Kind): Promise { - const api = getAPIParams(kind); - if(!api) { - return; - } - - if(!k8sCustomApi) { - return; - } - - try { - const result = await k8sCustomApi.getNamespacedCustomObject(api.group, api.version, namespace, api.plural, name); - const kbody = result.body as KubernetesListObject; - if (kbody.items && kbody.items.length > 0) { - return kbody.items[0]; - } - } catch (error) { - return; - } -} - - -export async function k8sListNamespaces(): Promise { - if(!k8sCoreApi) { - return; - } - - try { - const result = await k8sCoreApi.listNamespace(); - - const kbody = result.body as KubernetesListObject; - return kbody.items.map(ns => { - // for some reason listNamespace kind is undefined - (ns as any).kind = 'Namespace'; - return ns; - }); - } catch (error) { - return; - } -} diff --git a/src/kuberesources.ts b/src/kuberesources.ts new file mode 100644 index 00000000..b301c1df --- /dev/null +++ b/src/kuberesources.ts @@ -0,0 +1,42 @@ +import { QuickPickItem } from 'vscode'; + +/** + * A class from Kubernetes resources. + * Needed to pass arguments to that extension. + */ +export class ResourceKind implements QuickPickItem { + constructor(readonly displayName: string, readonly pluralDisplayName: string, readonly manifestKind: string, readonly abbreviation: string, readonly apiName?: string) { + } + + get label() { + return this.displayName; + } + get description() { + return ''; + } +} + +/** + * Kubernetes resource kinds implementing {@link QuickPickItem} + */ +export const allKinds = { + // endpoint: new ResourceKind('Endpoint', 'Endpoints', 'Endpoint', 'endpoints', 'endpoints'), + // namespace: new ResourceKind('Namespace', 'Namespaces', 'Namespace', 'namespace' , 'namespaces'), + // node: new ResourceKind('Node', 'Nodes', 'Node', 'node', 'nodes'), + deployment: new ResourceKind('Deployment', 'Deployments', 'Deployment', 'deployment', 'deployments'), + // daemonSet: new ResourceKind('DaemonSet', 'DaemonSets', 'DaemonSet', 'daemonset', 'daemonsets'), + // replicaSet: new ResourceKind('ReplicaSet', 'ReplicaSets', 'ReplicaSet', 'rs', 'replicasets'), + // replicationController: new ResourceKind('Replication Controller', 'Replication Controllers', 'ReplicationController', 'rc', 'replicationcontrollers'), + // job: new ResourceKind('Job', 'Jobs', 'Job', 'job', 'jobs'), + // cronjob: new ResourceKind('CronJob', 'CronJobs', 'CronJob', 'cronjob', 'cronjobs'), + pod: new ResourceKind('Pod', 'Pods', 'Pod', 'pod', 'pods'), + // crd: new ResourceKind('Custom Resource', 'Custom Resources', 'CustomResourceDefinition', 'crd', 'customresources'), + // service: new ResourceKind('Service', 'Services', 'Service', 'service', 'services'), + // configMap: new ResourceKind('ConfigMap', 'Config Maps', 'ConfigMap', 'configmap', 'configmaps'), + // secret: new ResourceKind('Secret', 'Secrets', 'Secret', 'secret', 'secrets'), + // ingress: new ResourceKind('Ingress', 'Ingress', 'Ingress', 'ingress', 'ingress'), + // persistentVolume: new ResourceKind('Persistent Volume', 'Persistent Volumes', 'PersistentVolume', 'pv', 'persistentvolumes'), + // persistentVolumeClaim: new ResourceKind('Persistent Volume Claim', 'Persistent Volume Claims', 'PersistentVolumeClaim', 'pvc', 'persistentvolumeclaims'), + // storageClass: new ResourceKind('Storage Class', 'Storage Classes', 'StorageClass', 'sc', 'storageclasses'), + // statefulSet: new ResourceKind('StatefulSet', 'StatefulSets', 'StatefulSet', 'statefulset', 'statefulsets'), +}; diff --git a/src/kubernetes/kubernetesTools.ts b/src/kubernetes/kubernetesTools.ts new file mode 100644 index 00000000..825086f6 --- /dev/null +++ b/src/kubernetes/kubernetesTools.ts @@ -0,0 +1,625 @@ +import { KubernetesListObject, KubernetesObject } from '@kubernetes/client-node'; +import { Uri, window } from 'vscode'; +import * as kubernetes from 'vscode-kubernetes-tools-api'; +import safesh from 'shell-escape-tag'; +import { AzureConstants } from '../azure/azureTools'; +import { Errorable, failed, succeeded } from '../errorable'; +import { globalState, telemetry } from '../extension'; +import { output } from '../output'; +import { shellCodeError } from '../shell'; +import { TelemetryErrorEventNames } from '../telemetry'; +import { parseJson } from '../utils/jsonUtils'; +import { ContextTypes, setVSCodeContext } from '../vscodeContext'; +import { BucketResult } from './types/flux/bucket'; +import { GitRepositoryResult } from './types/flux/gitRepository'; +import { HelmReleaseResult } from './types/flux/helmRelease'; +import { HelmRepositoryResult } from './types/flux/helmRepository'; +import { OCIRepositoryResult } from './types/flux/ociRepository'; +import { KubernetesConfig, KubernetesContextWithCluster } from './types/kubernetesConfig'; +import { KubernetesFileSchemes } from './types/kubernetesFileSchemes'; +import { ClusterProvider, ConfigMap, DeploymentResult, NamespaceResult, NodeResult, PodResult } from './types/kubernetesTypes'; +import { KustomizeResult } from './types/flux/kustomize'; +import { GitOpsTemplateResult } from './types/flux/gitOpsTemplate'; + +/** + * Defines Kubernetes Tools class for integration + * with Microsoft Kubernetes Tools extension API. + * @see https://github.com/Azure/vscode-kubernetes-tools + * @see https://github.com/Azure/vscode-kubernetes-tools-api + */ +class KubernetesTools { + + /** + * Keep a reference to the Kubernetes extension api. + */ + private kubectlApi?: kubernetes.KubectlV1; + + /** + * Current cluster supported kubernetes resource kinds. + */ + private clusterSupportedResourceKinds?: string[]; + /** + * RegExp for the Error that should not be sent in telemetry. + * Server doesn't have a resource type = when GitOps not enabled + * No connection could be made... = when cluster not running + */ + private notAnErrorServerDoesntHaveResourceTypeRegExp = /the server doesn't have a resource type/i; + private notAnErrorServerNotRunning = /no connection could be made because the target machine actively refused it/i; + /** + * Gets kubernetes tools extension kubectl api reference. + * @see https://github.com/Azure/vscode-kubernetes-tools-api + */ + async getKubectlApi() { + if (this.kubectlApi) { + return this.kubectlApi; + } + + const kubectl = await kubernetes.extension.kubectl.v1; + if (!kubectl.available) { + window.showErrorMessage(`Kubernetes Tools Kubectl API is unavailable: ${kubectl.reason}`); + telemetry.sendError(TelemetryErrorEventNames.KUBERNETES_TOOLS_API_UNAVAILABLE, new Error(kubectl.reason)); + return; + } + this.kubectlApi = kubectl.api; + return this.kubectlApi; + } + + /** + * Invokes kubectl command via Kubernetes Tools API. + * @param command Kubectl command to run. + * @returns Kubectl command results. + */ + async invokeKubectlCommand(command: string): Promise { + const kubectl = await this.getKubectlApi(); + if (!kubectl) { + return; + } + + const kubectlShellResult = await kubectl.invokeCommand(command); + + output.send(`> kubectl ${command}`, { + channelName: 'GitOps: kubectl', + newline: 'single', + revealOutputView: false, + }); + + if (kubectlShellResult?.code === 0) { + output.send(kubectlShellResult.stdout, { + channelName: 'GitOps: kubectl', + revealOutputView: false, + }); + } else { + output.send(kubectlShellResult?.stderr || '', { + channelName: 'GitOps: kubectl', + revealOutputView: false, + logLevel: 'error', + }); + } + + return kubectlShellResult; + } + + /** + * Gets current kubectl config with available contexts and clusters. + */ + async getKubectlConfig(): Promise> { + const configShellResult = await this.invokeKubectlCommand('config view -o json'); + + if (configShellResult?.code !== 0) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_KUBECTL_CONFIG); + return { + succeeded: false, + error: [shellCodeError(configShellResult)], + }; + } + + const kubectlConfig = parseJson(configShellResult.stdout); + return { + succeeded: true, + result: kubectlConfig, + }; + } + + /** + * Gets current kubectl context name. + */ + async getCurrentContext(): Promise> { + + const currentContextShellResult = await this.invokeKubectlCommand('config current-context'); + if (currentContextShellResult?.code !== 0) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_CURRENT_KUBERNETES_CONTEXT); + console.warn(`Failed to get current kubectl context: ${currentContextShellResult?.stderr}`); + setVSCodeContext(ContextTypes.NoClusterSelected, true); + return { + succeeded: false, + error: [`${currentContextShellResult?.code || ''} ${currentContextShellResult?.stderr}`], + }; + } + + const currentContext = currentContextShellResult.stdout.trim(); + setVSCodeContext(ContextTypes.NoClusterSelected, false); + + return { + succeeded: true, + result: currentContext, + }; + } + + /** + * Sets current kubectl context. + * @param contextName Kubectl context name to use. + * @returns `undefined` in case of an error or Object with information about + * whether or not context was switched or didn't need it (current). + */ + async setCurrentContext(contextName: string): Promise { + const currentContextResult = await this.getCurrentContext(); + if (succeeded(currentContextResult) && currentContextResult.result === contextName) { + return { + isChanged: false, + }; + } + + const setContextShellResult = await this.invokeKubectlCommand(safesh`config use-context ${contextName}`); + if (setContextShellResult?.stderr) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_SET_CURRENT_KUBERNETES_CONTEXT); + window.showErrorMessage(`Failed to set kubectl context to ${contextName}: ${setContextShellResult?.stderr}`); + return; + } + + setVSCodeContext(ContextTypes.NoClusterSelected, false); + setVSCodeContext(ContextTypes.CurrentClusterGitOpsNotEnabled, false); + setVSCodeContext(ContextTypes.NoSources, false); + setVSCodeContext(ContextTypes.NoWorkloads, false); + setVSCodeContext(ContextTypes.FailedToLoadClusterContexts, false); + this.clusterSupportedResourceKinds = undefined; + + return { + isChanged: true, + }; + } + + /** + * Get a list of contexts from kubeconfig. + * Also add cluster info to the context objects. + */ + async getContexts(): Promise> { + const kubectlConfig = await this.getKubectlConfig(); + + if (failed(kubectlConfig)) { + return { + succeeded: false, + error: kubectlConfig.error, + }; + } + if (!kubectlConfig.result.contexts) { + return { + succeeded: false, + error: ['Config fetched, but contexts not found.'], + }; + } + + const contexts: KubernetesContextWithCluster[] = kubectlConfig.result.contexts.map((context: KubernetesContextWithCluster) => { + const clusterInfo = kubectlConfig.result.clusters?.find(cluster => cluster.name === context.context.cluster); + if (clusterInfo) { + context.context.clusterInfo = clusterInfo; + } + return context; + }); + + return { + succeeded: true, + result: contexts, + }; + } + + async getClusterName(contextName: string): Promise { + const contexts = await this.getContexts(); + if(contexts.succeeded === true) { + return contexts.result.find(context => context.name === contextName)?.context.clusterInfo?.name || contextName; + } else { + return contextName; + } + } + + + /** + * Get pods by a deployment name. + * @param name pod target name + * @param namespace pod target namespace + */ + async getPodsOfADeployment(name = '', namespace = ''): Promise { + let nameArg: string; + + if (name === 'fluxconfig-agent' || name === 'fluxconfig-controller') { + nameArg = name ? `-l app.kubernetes.io/component=${name}` : ''; + } else { + nameArg = name ? `-l app=${name}` : ''; + } + + let namespaceArg = ''; + if (namespace === 'all') { + namespaceArg = '--all-namespaces'; + } else if (namespace.length > 0) { + namespaceArg = `--namespace=${namespace}`; + } + + const podResult = await this.invokeKubectlCommand(`get pod ${nameArg} ${namespaceArg} -o json`); + + if (podResult?.code !== 0) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_PODS_OF_A_DEPLOYMENT); + console.warn(`Failed to get pods: ${podResult?.stderr}`); + return; + } + + return parseJson(podResult?.stdout); + } + + /** + * Gets all kustomizations for the current kubectl context. + */ + async getKustomizations(): Promise { + const kustomizationShellResult = await this.invokeKubectlCommand('get Kustomization -A -o json'); + if (kustomizationShellResult?.code !== 0) { + console.warn(`Failed to get kubectl kustomizations: ${kustomizationShellResult?.stderr}`); + if (kustomizationShellResult?.stderr && !this.notAnErrorServerDoesntHaveResourceTypeRegExp.test(kustomizationShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_KUSTOMIZATIONS); + } + return; + } + return parseJson(kustomizationShellResult.stdout); + } + + /** + * Gets all helm releases from the current kubectl context. + */ + async getHelmReleases(): Promise { + const helmReleaseShellResult = await this.invokeKubectlCommand('get helmreleases.helm.toolkit.fluxcd.io -A -o json'); + if (helmReleaseShellResult?.code !== 0) { + console.warn(`Failed to get kubectl helm releases: ${helmReleaseShellResult?.stderr}`); + if (helmReleaseShellResult?.stderr && !this.notAnErrorServerDoesntHaveResourceTypeRegExp.test(helmReleaseShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_HELM_RELEASES); + } + return; + } + return parseJson(helmReleaseShellResult.stdout); + } + + /** + * Gets all git repositories for the current kubectl context. + */ + async getGitRepositories(): Promise { + const gitRepositoryShellResult = await this.invokeKubectlCommand('get GitRepository -A -o json'); + if (gitRepositoryShellResult?.code !== 0) { + console.warn(`Failed to get kubectl git repositories: ${gitRepositoryShellResult?.stderr}`); + if (gitRepositoryShellResult?.stderr && !this.notAnErrorServerDoesntHaveResourceTypeRegExp.test(gitRepositoryShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_GIT_REPOSITORIES); + } + return; + } + return parseJson(gitRepositoryShellResult.stdout); + } + + async getOciRepositories(): Promise { + const ociRepositoryShellResult = await this.invokeKubectlCommand('get OciRepository -A -o json'); + if (ociRepositoryShellResult?.code !== 0) { + console.warn(`Failed to get kubectl oci repositories: ${ociRepositoryShellResult?.stderr}`); + if (ociRepositoryShellResult?.stderr && !this.notAnErrorServerDoesntHaveResourceTypeRegExp.test(ociRepositoryShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_OCI_REPOSITORIES); + } + return; + } + return parseJson(ociRepositoryShellResult.stdout); + } + + /** + * Gets all helm repositories for the current kubectl context. + */ + async getHelmRepositories(): Promise { + const helmRepositoryShellResult = await this.invokeKubectlCommand('get HelmRepository -A -o json'); + if (helmRepositoryShellResult?.code !== 0) { + console.warn(`Failed to get kubectl helm repositories: ${helmRepositoryShellResult?.stderr}`); + if (helmRepositoryShellResult?.stderr && !this.notAnErrorServerDoesntHaveResourceTypeRegExp.test(helmRepositoryShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_HELM_REPOSITORIES); + } + return; + } + return parseJson(helmRepositoryShellResult.stdout); + } + + /** + * Gets all buckets for the current kubectl context. + */ + async getBuckets(): Promise { + const bucketShellResult = await this.invokeKubectlCommand('get Bucket -A -o json'); + if (bucketShellResult?.code !== 0) { + console.warn(`Failed to get kubectl buckets: ${bucketShellResult?.stderr}`); + if (bucketShellResult?.stderr && !this.notAnErrorServerDoesntHaveResourceTypeRegExp.test(bucketShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_BUCKETS); + } + return; + } + return parseJson(bucketShellResult.stdout); + } + + /** + * Gets all GitOpsTemplates for the current kubectl context. + */ + async getGitOpsTemplates(): Promise { + const result = await this.invokeKubectlCommand('get gitopstemplates -A -o json'); + if (result?.code !== 0) { + console.warn(`Failed to get kubectl gitopstemplates: ${result?.stderr}`); + if (result?.stderr && !this.notAnErrorServerDoesntHaveResourceTypeRegExp.test(result.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_GITOPSTEMPLATES); + } + return; + } + return parseJson(result.stdout); + } + + + + /** + * Get all flux system deployments. + */ + async getFluxControllers(context?: string): Promise { + const contextArg = context ? safesh`--context ${context}` : ''; + + const fluxDeploymentShellResult = await this.invokeKubectlCommand(`get deployment --namespace=flux-system ${contextArg} -o json`); + + if (fluxDeploymentShellResult?.code !== 0) { + console.warn(`Failed to get flux controllers: ${fluxDeploymentShellResult?.stderr}`); + if (fluxDeploymentShellResult?.stderr && !this.notAnErrorServerNotRunning.test(fluxDeploymentShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_FLUX_CONTROLLERS); + } + return; + } + + return parseJson(fluxDeploymentShellResult.stdout); + } + + /** + * Return true if gitops is enabled in the current cluster. + * Function checks if `flux-system` namespace contains flux controllers. + * @param contextName target cluster name + */ + async isGitOpsEnabled(contextName: string) { + const fluxControllers = await this.getFluxControllers(contextName); + + if (!fluxControllers) { + return; + } + + return fluxControllers.items.length !== 0; + } + + /** + * Return all available kubernetes resource kinds. + */ + async getAvailableResourceKinds(): Promise { + if (this.clusterSupportedResourceKinds) { + return this.clusterSupportedResourceKinds; + } + + const kindsShellResult = await this.invokeKubectlCommand('api-resources --verbs=list -o name'); + if (kindsShellResult?.code !== 0) { + this.clusterSupportedResourceKinds = undefined; + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_AVAILABLE_RESOURCE_KINDS); + console.warn(`Failed to get resource kinds: ${kindsShellResult?.stderr}`); + return; + } + + const kinds = kindsShellResult.stdout + .split('\n') + .filter(kind => kind.length); + + this.clusterSupportedResourceKinds = kinds; + return kinds; + } + + /** + * Return all kubernetes resources that were created by a kustomize/helmRelease. + * @param name name of the kustomize/helmRelease object + * @param namespace namespace of the kustomize/helmRelease object + */ + async getChildrenOfWorkload( + workload: 'kustomize' | 'helm', + name: string, + namespace: string, + ): Promise | undefined> { + const resourceKinds = await this.getAvailableResourceKinds(); + if (!resourceKinds) { + return; + } + + const query = `get ${resourceKinds.join(',')} -l ${workload}.toolkit.fluxcd.io/name=${name} -n ${namespace} -o json`; + const resourcesShellResult = await this.invokeKubectlCommand(query); + + if (!resourcesShellResult || resourcesShellResult.code !== 0) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_CHILDREN_OF_A_WORKLOAD); + window.showErrorMessage(`Failed to get ${workload} created resources: ${resourcesShellResult?.stderr}`); + return undefined; + } + + return parseJson(resourcesShellResult.stdout); + } + + /** + * Get namespaces from current context. + */ + async getNamespaces(): Promise { + const namespacesShellResult = await this.invokeKubectlCommand('get ns -o json'); + + if (namespacesShellResult?.code !== 0) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_NAMESPACES); + window.showErrorMessage(`Failed to get namespaces ${namespacesShellResult?.stderr}`); + return; + } + + return parseJson(namespacesShellResult.stdout); + } + + /** + * Try to detect known cluster providers. Returns user selected cluster type if that is set. + * @param context target context to get resources from. + * TODO: maybe use Errorable? + */ + async detectClusterProvider(context: string): Promise { + const clusterName = await kubernetesTools.getClusterName(context); + const clusterMetadata = globalState.getClusterMetadata(clusterName); + + if(clusterMetadata?.clusterProvider) { + return clusterMetadata.clusterProvider; + } + + const tryProviderAzureARC = await this.isClusterAzureARC(context); + if (tryProviderAzureARC === ClusterProvider.AzureARC) { + return ClusterProvider.AzureARC; + } else if (tryProviderAzureARC === ClusterProvider.DetectionFailed) { + return ClusterProvider.DetectionFailed; + } + + const tryProviderAKS = await this.isClusterAKS(context); + if (tryProviderAKS === ClusterProvider.AKS) { + return ClusterProvider.AKS; + } else if (tryProviderAKS === ClusterProvider.DetectionFailed) { + return ClusterProvider.DetectionFailed; + } + + return ClusterProvider.Generic; + } + + /** + * Try to determine if the cluster is AKS or not. + * @param context target context to get resources from. + */ + private async isClusterAKS(context: string): Promise { + const nodesShellResult = await this.invokeKubectlCommand(safesh`get nodes --context=${context} -o json`); + + if (nodesShellResult?.code !== 0) { + console.warn(`Failed to get nodes from "${context}" context to determine the cluster type. ${nodesShellResult?.stderr}`); + if (nodesShellResult?.stderr && !this.notAnErrorServerNotRunning.test(nodesShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_NODES_TO_DETECT_AKS_CLUSTER); + } + return ClusterProvider.DetectionFailed; + } + + const nodes: NodeResult | undefined = parseJson(nodesShellResult.stdout); + if (!nodes) { + return ClusterProvider.DetectionFailed; + } + const firstNode = nodes.items[0]; + + if (!firstNode) { + console.warn(`No nodes in the "${context}" context to determine the cluster type.`); + return ClusterProvider.DetectionFailed; + } + + const providerID = firstNode.spec?.providerID; + + if (providerID?.startsWith('azure:///')) { + return ClusterProvider.AKS; + } else { + return ClusterProvider.Generic; + } + } + + /** + * Try to determine if the cluster is managed by Azure ARC or not. + * @param context target context to get resources from. + */ + private async isClusterAzureARC(context: string): Promise { + const configmapShellResult = await this.invokeKubectlCommand(safesh`get configmaps azure-clusterconfig -n ${AzureConstants.ArcNamespace} --context=${context} --ignore-not-found -o json`); + + if (configmapShellResult?.code !== 0) { + if (configmapShellResult?.stderr && !this.notAnErrorServerNotRunning.test(configmapShellResult.stderr)) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_CONFIGMAPS_TO_DETECT_ARC_CLUSTER); + } + console.warn(`Failed to get configmaps from "${context}" context to determine the cluster type. ${configmapShellResult?.stderr}`); + return ClusterProvider.DetectionFailed; + } + + const stdout = configmapShellResult.stdout; + if (stdout.length) { + const azureClusterconfigConfigMap: ConfigMap | undefined = parseJson(stdout); + if (azureClusterconfigConfigMap === undefined) { + return ClusterProvider.DetectionFailed; + } else { + return ClusterProvider.AzureARC; + } + } + + return ClusterProvider.Generic; + } + + /** + * Gets resource Uri for loading kubernetes resource config in vscode editor. + * + * @see https://github.com/Azure/vscode-kubernetes-tools/blob/master/src/kuberesources.virtualfs.ts + * + * @param namespace Resource namespace. + * @param resourceName Resource name. + * @param documentFormat Resource document format. + * @param action Resource Uri action. + * @returns + */ + getResourceUri( + namespace: string | null | undefined, + resourceName: string | undefined, + documentFormat: string, + action?: string, + ): Uri { + + // determine resource file extension + let fileExtension = ''; + if (documentFormat !== '') { + fileExtension = `.${documentFormat}`; + } + + // create virtual document file name with extension + const documentName = `${resourceName?.replace('/', '-')}${fileExtension}`; + + // determine virtual resource file scheme + let scheme = KubernetesFileSchemes.Resource; + if (action === 'describe') { + scheme = KubernetesFileSchemes.ReadonlyResource; + } + + // determine virtual resource file authority + let authority: string = KubernetesFileSchemes.KubectlResource; + if (action === 'describe') { + authority = KubernetesFileSchemes.DescribeResource; + } + + // set namespace query param + let namespaceQuery = ''; + if (namespace) { + namespaceQuery = `ns=${namespace}&`; + } + + // create resource url + const nonce: number = new Date().getTime(); + const url = `${scheme}://${authority}/${documentName}?${namespaceQuery}value=${resourceName}&_=${nonce}`; + + // create resource uri + return Uri.parse(url); + } + + /** + * Get one resource object by kind/name and namespace + * @param name name of the target resource + * @param namespace namespace of the target resource + * @param kind kind of the target resource + */ + async getResource(name: string, namespace: string, kind: string): Promise { + const resourceShellResult = await this.invokeKubectlCommand(`get ${kind}/${name} --namespace=${namespace} -o json`); + if (resourceShellResult?.code !== 0) { + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_GET_RESOURCE); + return; + } + + return parseJson(resourceShellResult.stdout); + } + +} + +export const kubernetesTools = new KubernetesTools(); diff --git a/src/utils/sortByMetadataName.ts b/src/kubernetes/kubernetesUtils.ts similarity index 82% rename from src/utils/sortByMetadataName.ts rename to src/kubernetes/kubernetesUtils.ts index e0a94fe6..44aeb545 100644 --- a/src/utils/sortByMetadataName.ts +++ b/src/kubernetes/kubernetesUtils.ts @@ -1,4 +1,4 @@ -import { KubernetesObject } from 'types/kubernetes/kubernetesTypes'; +import { KubernetesObject } from './types/kubernetesTypes'; export function sortByMetadataName(items: Type[]): Type[] { return items.sort((i1: any, i2: any) => { diff --git a/src/types/flux/bucket.ts b/src/kubernetes/types/flux/bucket.ts similarity index 78% rename from src/types/flux/bucket.ts rename to src/kubernetes/types/flux/bucket.ts index 87290f66..9c0fc659 100644 --- a/src/types/flux/bucket.ts +++ b/src/kubernetes/types/flux/bucket.ts @@ -1,12 +1,25 @@ -import { Artifact } from 'types/flux/artifact'; -import { Condition, Kind, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; +import { Artifact, DeploymentCondition, KubernetesObject, KubernetesObjectKinds, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; +/** + * Buckets result from running + * `kubectl get Bucket -A` command. + */ +export interface BucketResult { + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.List; + readonly items: Bucket[]; + readonly metadata: ResultMetadata; +} /** * Bucket info object. */ export interface Bucket extends KubernetesObject { - readonly kind: Kind.Bucket; + + // standard kubernetes object fields + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.Bucket; + readonly metadata: ObjectMeta; /** * Bucket spec details. @@ -83,7 +96,7 @@ export interface Bucket extends KubernetesObject { /** * Conditions holds the conditions for the Bucket */ - readonly conditions?: Condition[]; + readonly conditions?: DeploymentCondition[]; /** * URL is the download link for the artifact output of the last Bucket sync diff --git a/src/kubernetes/types/flux/gitOpsTemplate.ts b/src/kubernetes/types/flux/gitOpsTemplate.ts new file mode 100644 index 00000000..5f26d14d --- /dev/null +++ b/src/kubernetes/types/flux/gitOpsTemplate.ts @@ -0,0 +1,39 @@ +import { KubernetesObject, KubernetesObjectKinds, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; + +/** + * `kubectl get GitOpsTemplate -A` command. + */ +export interface GitOpsTemplateResult { + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.List; + readonly items: GitOpsTemplate[]; + readonly metadata: ResultMetadata; +} + + +export interface GitOpsTemplate extends KubernetesObject { + // standard kubernetes object fields + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.GitOpsTemplate; + readonly metadata: ObjectMeta; + + readonly spec: { + readonly params?: TemplateParam[]; + readonly description?: string; + }; + + readonly status?: any; +} + +/** + * params spec for GitOpsTempalte + */ +export interface TemplateParam { + readonly name: string; + readonly description: string; + readonly options?: string[]; + readonly default?: string; + readonly required?: boolean; +} + + diff --git a/src/types/flux/gitRepository.ts b/src/kubernetes/types/flux/gitRepository.ts similarity index 87% rename from src/types/flux/gitRepository.ts rename to src/kubernetes/types/flux/gitRepository.ts index 0809ed5f..ecf55b00 100644 --- a/src/types/flux/gitRepository.ts +++ b/src/kubernetes/types/flux/gitRepository.ts @@ -1,13 +1,25 @@ -import { Artifact } from 'types/flux/artifact'; -import { Condition, Kind, KubernetesObject, LocalObjectReference } from 'types/kubernetes/kubernetesTypes'; +import { Artifact, DeploymentCondition, KubernetesObject, KubernetesObjectKinds, LocalObjectReference, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; +/** + * Git repositories result from running + * `kubectl get GitRepository -A` command. + */ +export interface GitRepositoryResult { + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.List; + readonly items: GitRepository[]; + readonly metadata: ResultMetadata; +} /** * Git repository info object. */ export interface GitRepository extends KubernetesObject { - readonly kind: Kind.GitRepository; + // standard kubernetes object fields + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.GitRepository; + readonly metadata: ObjectMeta; /** * Git repository spec details. @@ -93,7 +105,7 @@ export interface GitRepository extends KubernetesObject { /** * Conditions holds the conditions for the GitRepository */ - readonly conditions?: Condition[]; + readonly conditions?: DeploymentCondition[]; /** * URL is the download link for the artifact output of the last repository sync diff --git a/src/types/flux/helmRelease.ts b/src/kubernetes/types/flux/helmRelease.ts similarity index 94% rename from src/types/flux/helmRelease.ts rename to src/kubernetes/types/flux/helmRelease.ts index e4837fe5..ce7da031 100644 --- a/src/types/flux/helmRelease.ts +++ b/src/kubernetes/types/flux/helmRelease.ts @@ -1,11 +1,26 @@ -import { Condition, Kind, KubernetesJSON, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { DependsOn, Kustomization, KustomizationKubeConfig, NamespacedObjectKindReference } from './kustomization'; +import { DeploymentCondition, KubernetesJSON, KubernetesObject, KubernetesObjectKinds, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; +import { DependsOn, KubeConfig, Kustomize, NamespacedObjectKindReference } from './kustomize'; + +/** + * Helm releases result from running + * `kubectl get helmreleases.helm.toolkit.fluxcd.io -A` command. + */ +export interface HelmReleaseResult { + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.List; + readonly items: HelmRelease[]; + readonly metadata: ResultMetadata; +} /** * Helm release info object. */ export interface HelmRelease extends KubernetesObject { - readonly kind: Kind.HelmRelease; + + // standard kubernetes object fields + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.HelmRelease; + readonly metadata: ObjectMeta; /** * Helm release spec details. @@ -28,7 +43,7 @@ export interface HelmRelease extends KubernetesObject { * KubeConfig for reconciling the HelmRelease on a remote cluster. * When specified, KubeConfig takes precedence over ServiceAccountName. */ - readonly kubeConfig?: KustomizationKubeConfig; + readonly kubeConfig?: KubeConfig; /** * Suspend tells the controller to suspend reconciliation @@ -139,7 +154,7 @@ export interface HelmRelease extends KubernetesObject { /** * Conditions holds the conditions for the HelmRelease */ - readonly conditions?: Condition; + readonly conditions?: DeploymentCondition; /** * LastAppliedRevision is the revision of the last successfully applied source @@ -556,8 +571,8 @@ interface PostRenderer { * Kustomization to apply as PostRenderer */ readonly kustomize: { - readonly patchesStrategicMerge?: Kustomization['spec']['patchesStrategicMerge']; - readonly patchesJson6902?: Kustomization['spec']['patchesJson6902']; - readonly images?: Kustomization['spec']['images']; + readonly patchesStrategicMerge?: Kustomize['spec']['patchesStrategicMerge']; + readonly patchesJson6902?: Kustomize['spec']['patchesJson6902']; + readonly images?: Kustomize['spec']['images']; }; } diff --git a/src/types/flux/helmRepository.ts b/src/kubernetes/types/flux/helmRepository.ts similarity index 77% rename from src/types/flux/helmRepository.ts rename to src/kubernetes/types/flux/helmRepository.ts index 4da89cdc..1bc4f787 100644 --- a/src/types/flux/helmRepository.ts +++ b/src/kubernetes/types/flux/helmRepository.ts @@ -1,12 +1,25 @@ -import { Artifact } from 'types/flux/artifact'; -import { Condition, Kind, KubernetesObject, LocalObjectReference } from 'types/kubernetes/kubernetesTypes'; +import { Artifact, DeploymentCondition, KubernetesObject, KubernetesObjectKinds, LocalObjectReference, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; +/** + * Helm repositories result from running + * `kubectl get HelmRepository -A` command. + */ +export interface HelmRepositoryResult { + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.List; + readonly items: HelmRepository[]; + readonly metadata: ResultMetadata; +} /** * Helm repository info object. */ export interface HelmRepository extends KubernetesObject { - readonly kind: Kind.HelmRepository; + + // standard kubernetes object fields + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.HelmRepository; + readonly metadata: ObjectMeta; /** * Helm repository spec details. @@ -73,7 +86,7 @@ export interface HelmRepository extends KubernetesObject { /** * Conditions holds the conditions for the HelmRepository */ - readonly conditions?: Condition[]; + readonly conditions?: DeploymentCondition[]; /** * URL is the download link for the last index fetched diff --git a/src/types/flux/kustomization.ts b/src/kubernetes/types/flux/kustomize.ts similarity index 91% rename from src/types/flux/kustomization.ts rename to src/kubernetes/types/flux/kustomize.ts index 7a5619a9..2893b60d 100644 --- a/src/types/flux/kustomization.ts +++ b/src/kubernetes/types/flux/kustomize.ts @@ -1,14 +1,27 @@ -import { Condition, Kind, KubernetesJSON, KubernetesObject, LocalObjectReference } from 'types/kubernetes/kubernetesTypes'; +import { DeploymentCondition, KubernetesJSON, KubernetesObject, KubernetesObjectKinds, LocalObjectReference, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; +/** + * Kustomizations result from running + * `kubectl get Kustomization -A` command. + */ +export interface KustomizeResult { + readonly apiVersion: string; + readonly kind: 'List'; + readonly items: Kustomize[]; + readonly metadata: ResultMetadata; +} /** * Deployment kustomization info object. * * @see https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/deployment-v1/#Deployment */ -export interface Kustomization extends KubernetesObject { - readonly kind: Kind.Kustomization; +export interface Kustomize extends KubernetesObject { + // standard kubernetes object fields + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.Kustomization; + readonly metadata: ObjectMeta; /** * Deployment kustomization spec details. @@ -45,7 +58,7 @@ export interface Kustomization extends KubernetesObject { /** * KubeConfig references a Kubernetes secret that contains a kubeconfig file */ - readonly kubeConfig?: KustomizationKubeConfig; + readonly kubeConfig?: KubeConfig; /** * Path to the directory containing the kustomization.yaml file, @@ -143,7 +156,7 @@ export interface Kustomization extends KubernetesObject { /** * DeploymentCondition describes the state of a deployment at a certain point. */ - readonly conditions?: Condition[]; + readonly conditions?: DeploymentCondition[]; /** * The last successfully applied revision. The revision format for Git sources is @@ -321,7 +334,7 @@ interface Snapshot { }[]; } -export interface KustomizationKubeConfig { +export interface KubeConfig { /** * SecretRef holds the name to a secret that contains a ‘value’ key diff --git a/src/kubernetes/types/flux/object.ts b/src/kubernetes/types/flux/object.ts new file mode 100644 index 00000000..1eaf93e1 --- /dev/null +++ b/src/kubernetes/types/flux/object.ts @@ -0,0 +1,25 @@ +import { FluxWorkload } from '../../../flux/fluxTypes'; +import { KubernetesObjectKinds } from '../kubernetesTypes'; +import { Bucket } from './bucket'; +import { GitRepository } from './gitRepository'; +import { HelmRelease } from './helmRelease'; +import { HelmRepository } from './helmRepository'; +import { Kustomize } from './kustomize'; +import { OCIRepository } from './ociRepository'; + +export type FluxSourceObject = GitRepository | OCIRepository | HelmRepository | Bucket; +export type FluxWorkloadObject = Kustomize | HelmRelease; + +export const FluxSourceKinds: string[] = [ + KubernetesObjectKinds.GitRepository, + KubernetesObjectKinds.OCIRepository, + KubernetesObjectKinds.HelmRepository, + KubernetesObjectKinds.Bucket, +]; + + +export function namespacedObject(resource?: FluxSourceObject | FluxWorkloadObject): string | undefined { + if(resource) { + return `${resource.kind}/${resource.metadata.name}.${resource.metadata.namespace}`; + } +} diff --git a/src/types/flux/ociRepository.ts b/src/kubernetes/types/flux/ociRepository.ts similarity index 82% rename from src/types/flux/ociRepository.ts rename to src/kubernetes/types/flux/ociRepository.ts index bf4de897..37aeeb7b 100644 --- a/src/types/flux/ociRepository.ts +++ b/src/kubernetes/types/flux/ociRepository.ts @@ -1,12 +1,25 @@ -import { Artifact } from 'types/flux/artifact'; -import { Condition, Kind, KubernetesObject, LocalObjectReference } from 'types/kubernetes/kubernetesTypes'; +import { Artifact, DeploymentCondition, KubernetesObject, KubernetesObjectKinds, LocalObjectReference, ObjectMeta, ResultMetadata } from '../kubernetesTypes'; +/** + * OCI repositories result from running + * `kubectl get OCIRepository -A` command. + */ +export interface OCIRepositoryResult { + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.List; + readonly items: OCIRepository[]; + readonly metadata: ResultMetadata; +} /** * Helm repository info object. */ export interface OCIRepository extends KubernetesObject { - readonly kind: Kind.OCIRepository; + + // standard kubernetes object fields + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.OCIRepository; + readonly metadata: ObjectMeta; /** * Oci repository spec details. @@ -102,7 +115,7 @@ export interface OCIRepository extends KubernetesObject { /** * Conditions holds the conditions for the HelmRepository */ - readonly conditions?: Condition[]; + readonly conditions?: DeploymentCondition[]; /** * URL is the download link for the last index fetched diff --git a/src/kubernetes/types/kubernetesConfig.ts b/src/kubernetes/types/kubernetesConfig.ts new file mode 100644 index 00000000..3be7104d --- /dev/null +++ b/src/kubernetes/types/kubernetesConfig.ts @@ -0,0 +1,47 @@ +/** + * Kubernetes config result from + * running `kubectl config view` command. + */ +export interface KubernetesConfig { + readonly apiVersion: string; + readonly 'current-context': string; + readonly clusters: KubernetesCluster[] | undefined; + readonly contexts: KubernetesContext[] | undefined; + readonly users: { + readonly name: string; + readonly user: Record; + }[] | undefined; +} + +/** + * Cluster info object. + */ +export interface KubernetesCluster { + readonly name: string; + readonly cluster: { + readonly server: string; + readonly 'certificate-authority'?: string; + readonly 'certificate-authority-data'?: string; + }; +} + +/** + * Context info object. + */ +export interface KubernetesContext { + readonly name: string; + readonly context: { + readonly cluster: string; + readonly user: string; + readonly namespace?: string; + }; +} + +/** + * Kubernetes context but with cluster object inside it. + */ +export type KubernetesContextWithCluster = KubernetesContext & { + context: { + clusterInfo?: KubernetesCluster; + }; +}; diff --git a/src/types/kubernetes/kubernetesFileSchemes.ts b/src/kubernetes/types/kubernetesFileSchemes.ts similarity index 100% rename from src/types/kubernetes/kubernetesFileSchemes.ts rename to src/kubernetes/types/kubernetesFileSchemes.ts diff --git a/src/kubernetes/types/kubernetesTypes.ts b/src/kubernetes/types/kubernetesTypes.ts new file mode 100644 index 00000000..7ef606c0 --- /dev/null +++ b/src/kubernetes/types/kubernetesTypes.ts @@ -0,0 +1,550 @@ +import { V1ConfigMap, V1Deployment, V1Namespace, V1Node, V1Pod } from '@kubernetes/client-node'; + +/** + * Defines base Kubernetes object with common fields. + * + * @see https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields + */ +export interface KubernetesObject { + readonly apiVersion: string; + readonly kind: string; + readonly metadata: unknown; + readonly spec: unknown; +} + +/** + * Make a kubernetes list out of kubernetes resource type. + */ +interface KubernetesList { + readonly apiVersion: string; + readonly kind: KubernetesObjectKinds.List; + readonly items: T[]; + readonly metadata: ResultMetadata; +} + +// Fix types from `@kubernetes/client-node` +export type Namespace = Required & { + readonly kind: KubernetesObjectKinds.Namespace; +}; +export type NamespaceResult = KubernetesList; + +export type Deployment = Required & { + readonly kind: KubernetesObjectKinds.Deployment; +}; +export type DeploymentResult = KubernetesList; + +export type ConfigMap = Required & { + readonly kind: KubernetesObjectKinds.ConfigMap; +}; +export type ConfigMapResult = KubernetesList; + +export type Node = Required & { + readonly kind: KubernetesObjectKinds.Node; +}; +export type NodeResult = KubernetesList; + +export type Pod = Required & { + readonly kind: KubernetesObjectKinds.Pod; +}; +export type PodResult = KubernetesList; + +/** + * Defines supported Kubernetes object kinds. + */ +export const enum KubernetesObjectKinds { + List = 'List', + Bucket = 'Bucket', + GitRepository = 'GitRepository', + OCIRepository = 'OCIRepository', + HelmRepository = 'HelmRepository', + HelmRelease = 'HelmRelease', + Kustomization = 'Kustomization', + Deployment = 'Deployment', + Namespace = 'Namespace', + Node = 'Node', + Pod = 'Pod', + + ConfigMap = 'ConfigMap', + + GitOpsTemplate = 'GitOpsTemplate', +} + + +export const enum SourceObjectKinds { + Bucket = 'Bucket', + GitRepository = 'GitRepository', + OCIRepository = 'OCIRepository', + HelmRepository = 'HelmRepository', +} + + + +export interface ResultMetadata { + + /** + * Version of this resource as stored in the underlying database. + */ + readonly resourceVersion: ''; + + /** + * Deprecated in Kubernetes 1.16. Removed in Kubernetes 1.21. + */ + readonly selfLink?: ''; +} + +/** + * DeploymentCondition describes the state of a deployment at a certain point. + */ +export interface DeploymentCondition { + + /** + * Last time the condition transitioned from one status to another + */ + lastTransitionTime?: string; + + /** + * The last time this condition was updated + */ + lastUpdateTime?: string; + + /** + * A human readable message indicating details about the transition + */ + message?: string; + + /** + * The reason for the condition's last transition + */ + reason?: string; + + /** + * Status of the condition, one of True, False, Unknown + */ + status: string; + + /** + * Type of deployment condition + */ + type: string; +} + +/** + * LocalObjectReference contains enough information + * to let you locate the referenced object inside the same namespace. + */ +export interface LocalObjectReference { + + /** + * Name of the referent. + * @see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + */ + name: string; +} + +/** + * JSON represents any valid JSON value. These types are supported: + * bool, int64, float64, string, []interface{}, map[string]interface{} and nil. + */ +export interface KubernetesJSON { + [key: string]: unknown; +} + +/** + * Artifact represents the output of a source synchronisation. + */ +export interface Artifact { + + /** + * Path is the relative file path of this artifact + */ + readonly path: string; + + /** + * URL is the HTTP address of this artifact + */ + readonly url: string; + + /** + * Revision is a human readable identifier traceable in the origin source system. + * It can be a Git commit SHA, Git tag, a Helm index timestamp, + * a Helm chart version, etc. + */ + readonly revision?: string; + + /** + * Checksum is the SHA1 checksum of the artifact + */ + readonly checksum?: string; + + /** + * LastUpdateTime is the timestamp corresponding to the last update of this artifact + */ + readonly lastUpdateTime: string; +} + +/** + * ObjectMeta is metadata that all persisted resources must have, + * which includes all objects users must create. + */ +export interface ObjectMeta { + + /** + * Annotations is an unstructured key value map stored with a resource + * that may be set by external tools to store and retrieve arbitrary metadata. + * They are not queryable and should be preserved when modifying objects. + * @see http://kubernetes.io/docs/user-guide/annotations + */ + annotations?: { [key: string]: string; }; + + /** + * The name of the cluster which the object belongs to. + * This is used to distinguish resources with same name + * and namespace in different clusters. + * This field is not set anywhere right now, + * and apiserver is going to ignore it + * if set in create or update request. + */ + clusterName?: string; + + /** + * CreationTimestamp is a timestamp representing the server time + * when this object was created. It is not guaranteed to be set + * in happens-before order across separate operations. + * Clients may not set this value. + * It is represented in RFC3339 form and is in UTC. + * + * Populated by the system. Read-only. Null for lists. + * @see https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + */ + creationTimestamp?: string; + + /** + * Number of seconds allowed for this object to gracefully terminate + * before it will be removed from the system. + * Only set when deletionTimestamp is also set. + * May only be shortened. Read-only. + */ + deletionGracePeriodSeconds?: number; + + /** + * DeletionTimestamp is RFC 3339 date and time + * at which this resource will be deleted. + * + * This field is set by the server + * when a graceful deletion is requested by the user, + * and is not directly settable by a client. + * + * The resource is expected to be deleted + * (no longer visible from resource lists, and not reachable by name) + * after the time in this field, once the finalizers list is empty. + * As long as the finalizers list contains items, deletion is blocked. + * + * Once the deletionTimestamp is set, + * this value may not be unset or be set further into the future, + * although it may be shortened or the resource may be deleted prior to this time. + * For example, a user may request that a pod is deleted in 30 seconds. + * The Kubelet will react by sending a graceful termination signal + * to the containers in the pod. After that 30 seconds, + * the Kubelet will send a hard termination signal (SIGKILL) + * to the container and after cleanup, remove the pod from the API. + * In the presence of network partitions, + * this object may still exist after this timestamp, + * until an administrator or automated process can determine + * the resource is fully terminated. + * If not set, graceful deletion of the object has not been requested. + * + * Populated by the system when a graceful deletion is requested. Read-only. + * @see https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + */ + deletionTimestamp?: string; + + /** + * Must be empty before the object is deleted from the registry. + * Each entry is an identifier for the responsible component + * that will remove the entry from the list. + * If the deletionTimestamp of the object is non-nil, + * entries in this list can only be removed. + * Finalizers may be processed and removed in any order. + * Order is NOT enforced because it introduces significant risk of stuck finalizers. + * finalizers is a shared field, any actor with permission can reorder it. + * If the finalizer list is processed in order, + * then this can lead to a situation in which the component responsible + * for the first finalizer in the list is waiting for a signal + * (field value, external system, or other) produced by a component + * responsible for a finalizer later in the list, resulting in a deadlock. + * Without enforced ordering finalizers are free to order amongst themselves + * and are not vulnerable to ordering changes in the list. + */ + finalizers?: string[]; + + /** + * GenerateName is an optional prefix, used by the server, + * to generate a unique name ONLY IF the Name field has not been provided. + * If this field is used, the name returned to the client will be different than the name passed. + * + * This value will also be combined with a unique suffix. + * The provided value has the same validation rules as the Name field, + * and may be truncated by the length of the suffix required to make the value unique on the server. + * + * If this field is specified and the generated name exists, + * the server will NOT return a 409 - instead, + * it will either return 201 Created or 500 with Reason ServerTimeout + * indicating a unique name could not be found in the time allotted, + * and the client should retry (optionally after the time indicated in the Retry-After header). + * + * Applied only if Name is not specified. + * @see https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency + */ + generateName?: string; + + /** + * A sequence number representing a specific generation of the desired state. + * Populated by the system. Read-only. + */ + generation?: number; + + /** + * Map of string keys and values that can be used to organize + * and categorize (scope and select) objects. + * May match selectors of replication controllers and services. + * @see http://kubernetes.io/docs/user-guide/labels + */ + labels?: { [key: string]: string; }; + + /** + * ManagedFields maps workflow-id and version to the set of fields + * that are managed by that workflow. This is mostly for internal housekeeping, + * and users typically shouldn't need to set or understand this field. + * A workflow can be the user's name, a controller's name, + * or the name of a specific apply path like "ci-cd". + * The set of fields is always in the version + * that the workflow used when modifying the object. + */ + managedFields?: ManagedFieldsEntry[]; + + /** + * Name must be unique within a namespace. + * Is required when creating resources, + * although some resources may allow a client to request + * the generation of an appropriate name automatically. + * Name is primarily intended for creation idempotence and configuration definition. + * Cannot be updated. + * @see http://kubernetes.io/docs/user-guide/identifiers#names + */ + name?: string; + + /** + * Namespace defines the space within which each name must be unique. + * An empty namespace is equivalent to the "default" namespace, + * but "default" is the canonical representation. + * Not all objects are required to be scoped to a namespace - + * the value of this field for those objects will be empty. + * + * Must be a DNS_LABEL. Cannot be updated. + * @see http://kubernetes.io/docs/user-guide/namespaces + */ + namespace?: string; + + /** + * List of objects depended by this object. + * If ALL objects in the list have been deleted, + * this object will be garbage collected. + * If this object is managed by a controller, + * then an entry in this list will point to this controller, + * with the controller field set to true. + * There cannot be more than one managing controller. + */ + ownerReferences?: OwnerReference[]; + + /** + * An opaque value that represents the internal version of this object + * that can be used by clients to determine when objects have changed. + * May be used for optimistic concurrency, change detection, + * and the watch operation on a resource or set of resources. + * Clients must treat these values as opaque and passed unmodified back to the server. + * They may only be valid for a particular resource or set of resources. + * + * Populated by the system. Read-only. Value must be treated as opaque by clients. + * @see https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + */ + resourceVersion?: string; + + /** + * SelfLink is a URL representing this object. Populated by the system. Read-only. + * + * DEPRECATED Kubernetes will stop propagating this field in 1.20 release + * and the field is planned to be removed in 1.21 release. + */ + selfLink?: string; + + /** + * UID is the unique in time and space value for this object. + * It is typically generated by the server on successful creation of a resource + * and is not allowed to change on PUT operations. + * + * Populated by the system. Read-only. + * @see http://kubernetes.io/docs/user-guide/identifiers#uids + */ + uid?: string; +} + +/** + * OwnerReference contains enough information to let you identify an owning object. + * An owning object must be in the same namespace as the dependent, + * or be cluster-scoped, so there is no namespace field. + */ +interface OwnerReference { + + /** + * API version of the referent + */ + apiVersion: string; + + /** + * If true, AND if the owner has the "foregroundDeletion" finalizer, + * then the owner cannot be deleted from the key-value store until this reference is removed. + * Defaults to false. To set this field, a user needs "delete" permission of the owner, + * otherwise 422 (Unprocessable Entity) will be returned. + */ + blockOwnerDeletion?: boolean; + + /** + * If true, this reference points to the managing controller + */ + controller?: boolean; + + /** + * Kind of the referent. + * @see https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + */ + kind: string; + + /** + * Name of the referent. + * @see http://kubernetes.io/docs/user-guide/identifiers#names + */ + name: string; + + /** + * UID of the referent. + * @see http://kubernetes.io/docs/user-guide/identifiers#uids + */ + uid: string; +} + +/** + * ManagedFieldsEntry is a workflow-id, a FieldSet and the group version + * of the resource that the fieldset applies to. + */ +interface ManagedFieldsEntry { + + /** + * APIVersion defines the version of this resource that this field set applies to. + * The format is "group/version" just like the top-level APIVersion field. + * It is necessary to track the version of a field set because it cannot be automatically converted. + */ + apiVersion?: string; + + /** + * FieldsType is the discriminator for the different fields format and version. + * There is currently only one possible value: "FieldsV1". + */ + fieldsType?: string; + + /** + * FieldsV1 holds the first JSON version format as described in the "FieldsV1" type + */ + fieldsV1?: FieldsV1; + + /** + * Manager is an identifier of the workflow managing these fields + */ + manager?: string; + + /** + * Operation is the type of operation which lead to this ManagedFieldsEntry being created. + * The only valid values for this field are 'Apply' and 'Update'. + */ + operation?: string; + + /** + * Subresource is the name of the subresource used to update that object, + * or empty string if the object was updated through the main resource. + * The value of this field is used to distinguish between managers, + * even if they share the same name. + * For example, a status update will be distinct from a regular update + * using the same manager name. + * Note that the APIVersion field is not related to the Subresource field + * and it always corresponds to the version of the main resource. + */ + subresource?: string; + + /** + * Time is timestamp of when these fields were set. + * It should always be empty if Operation is 'Apply'. + */ + time?: string; +} + +/** + * FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. + * + * Each key is either a '.' representing the field itself, + * and will always map to an empty set, or a string representing a sub-field or item. + * The string will follow one of these four formats: + * 'f:', where is the name of a field in a struct, + * or key in a map 'v:', where is the exact json formatted value of a list item 'i:', + * where is position of a item in a list 'k:', + * where is a map of a list item's key fields to their unique values + * If a key maps to an empty Fields value, the field that key represents is part of the set. + * + * The exact format is defined in: + * @see https://sigs.k8s.io/structured-merge-diff + */ +interface FieldsV1 { + [key: string]: unknown; +} + +/** + * Cluster providers will have some differences. + * For example, AKS cluster has special handling + * for enabling GitOps. + */ +export const enum ClusterProvider { + /** + * Azure Kubernetes Service. + */ + AKS = 'AKS', + /** + * Cluster managed by Azure Arc. + */ + AzureARC = 'Azure Arc', + /** + * Any cluster that is not AKS and not Azure Arc is + * considered generic at this point. + */ + Generic = 'Generic', + /** + * Not loaded yet. + */ + Unknown = 'Unknown', + /** + * Error occurred when trying to determine the cluster provider + */ + DetectionFailed = 'DetectionFailed', +} + +export type KnownClusterProviders = Exclude, ClusterProvider.DetectionFailed>; +export const knownClusterProviders: KnownClusterProviders[] = [ + ClusterProvider.AKS, + ClusterProvider.AzureARC, + ClusterProvider.Generic, +]; + +export interface ClusterInfo { + contextName: string; + clusterName: string; + clusterProvider: ClusterProvider; + isClusterProviderUserOverride: boolean; + isAzure: boolean; +} diff --git a/src/cli/shell/output.ts b/src/output.ts similarity index 100% rename from src/cli/shell/output.ts rename to src/output.ts diff --git a/src/cli/shell/exec.ts b/src/shell.ts similarity index 69% rename from src/cli/shell/exec.ts rename to src/shell.ts index 0c16fcdb..3996b14b 100644 --- a/src/cli/shell/exec.ts +++ b/src/shell.ts @@ -1,16 +1,12 @@ -// kills them dead even if child processes are not joined correctly -import tkill from 'tree-kill'; -import isRunning from 'is-running'; - import { ChildProcess } from 'child_process'; import * as shelljs from 'shelljs'; -import { Progress, ProgressLocation, window, workspace } from 'vscode'; - - -import { GlobalStateKey } from 'data/globalState'; -import { globalState } from 'extension'; +import { workspace, Progress, ProgressLocation, window } from 'vscode'; +import { globalState } from './extension'; +import { GlobalStateKey } from './globalState'; import { output } from './output'; +// 🚧 WORK IN PROGRESS. + /** * Ignore `"vs-kubernetes.use-wsl" setting. * Always return false. @@ -43,7 +39,6 @@ export const enum Platform { export type ExecCallback = shelljs.ExecCallback; -type ProcCallback = (proc: ChildProcess)=> void; const WINDOWS = 'win32'; export interface ShellResult { @@ -63,14 +58,14 @@ export type ShellHandler = (code: number, stdout: string, stderr: string)=> void /** * Return `true` when user has windows OS. */ -export function isWindows(): boolean { +function isWindows(): boolean { return (process.platform === WINDOWS) && !getUseWsl(); } /** * Return `true` when user has Unix OS. */ -export function isUnix(): boolean { +function isUnix(): boolean { return !isWindows(); } @@ -78,7 +73,7 @@ export function isUnix(): boolean { * Return user platform. * For WSL - return Linux. */ -export function platform(): Platform { +function platform(): Platform { if (getUseWsl()) { return Platform.Linux; } @@ -113,16 +108,13 @@ function execOpts({ cwd }: { cwd?: string; } = {}): shelljs.ExecOptions { cwd: cwd, env: env, async: true, - silent: true, }; return opts; } -export async function exec( - cmd: string, - { cwd, callback }: { cwd?: string; callback?: ProcCallback; } = {}): Promise { +async function exec(cmd: string, { cwd }: { cwd?: string; } = {}): Promise { try { - return await execCore(cmd, execOpts({ cwd }), callback); + return await execCore(cmd, execOpts({ cwd }), null); } catch (e) { console.error(e); window.showErrorMessage(String(e)); @@ -138,7 +130,7 @@ export async function exec( * Execute command in cli and send the text to vscode output view. * @param cmd CLI command string */ -export async function execWithOutput( +async function execWithOutput( cmd: string, { revealOutputView = true, @@ -173,7 +165,6 @@ export async function execWithOutput( cwd: cwd, env: execOpts().env, }); - setExecTimeoutKill(childProcess); let stdout = ''; let stderr = ''; @@ -196,10 +187,6 @@ export async function execWithOutput( childProcess.on('exit', (code: number) => { output.send('\n', { newline: 'none', revealOutputView: false }); - if(code === null) { - stderr = `exec '${cmd}' timed out.\nSTDERR: ${stderr}`; - } - resolve({ code, stdout, @@ -210,16 +197,12 @@ export async function execWithOutput( } } -function execCore(cmd: string, opts: any, callback?: ProcCallback, stdin?: string): Promise { +function execCore(cmd: string, opts: any, callback?: ((proc: ChildProcess)=> void) | null, stdin?: string): Promise { return new Promise(resolve => { if (getUseWsl()) { cmd = `wsl ${cmd}`; } - const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => { - // console.warn('RESOLVE', cmd, code, stdout, stderr); - resolve({code : code, stdout : stdout, stderr : stderr}); - }); - setExecTimeoutKill(proc); + const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => resolve({code : code, stdout : stdout, stderr : stderr})); if (stdin) { proc.stdin?.end(stdin); } @@ -229,36 +212,11 @@ function execCore(cmd: string, opts: any, callback?: ProcCallback, stdin?: strin }); } -export function execProc(cmd: string): ChildProcess { - const opts = execOpts(); - if (getUseWsl()) { - cmd = `wsl ${cmd}`; - } - - const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => {}); - return proc; -} - -function setExecTimeoutKill(proc: ChildProcess) { - const timeout = workspace.getConfiguration('gitops').get('execTimeout') as string; - const timeoutSeconds = parseInt(timeout); - if (isNaN(timeoutSeconds) || timeoutSeconds === 0) { - return; - } - - setTimeout(() => { - if (proc.exitCode === null) { - console.warn('timeout SIGTERM', proc.pid, proc.spawnargs.join(' ')); - // make sure all child processes are killed - tkill(proc.pid, 15); - - setTimeout(() => { - if (isRunning(proc.pid)) { - console.warn('timeout SIGKILL', proc.pid, proc.spawnargs.join(' ')); - tkill(proc.pid, 9); - } - }, 2000); - } - }, timeoutSeconds * 1000); -} - +export const shell = { + isWindows : isWindows, + isUnix : isUnix, + platform : platform, + exec : exec, + // execCore : execCore, + execWithOutput: execWithOutput, +}; diff --git a/src/ui/statusBar.ts b/src/statusBar.ts similarity index 81% rename from src/ui/statusBar.ts rename to src/statusBar.ts index 40bdf22f..defe86cb 100644 --- a/src/ui/statusBar.ts +++ b/src/statusBar.ts @@ -6,6 +6,7 @@ class StatusBar { private statusBarItemName = 'gitops'; private numberOfLoadingTreeViews = 0; + private loadingWasHidden = false; constructor() { this.statusBarItem = window.createStatusBarItem( @@ -13,7 +14,7 @@ class StatusBar { StatusBarAlignment.Left, -1e10,// align to the right ); - this.statusBarItem.text = '$(sync~spin) GitOps: Loading Resources'; + this.statusBarItem.text = '$(sync~spin) GitOps: Initializing Tree Views'; } /** @@ -21,6 +22,10 @@ class StatusBar { * (only at the extension initialization (once)) */ startLoadingTree(): void { + if (this.loadingWasHidden) { + return; + } + this.numberOfLoadingTreeViews++; this.statusBarItem.show(); } @@ -33,7 +38,9 @@ class StatusBar { this.numberOfLoadingTreeViews--; if (this.numberOfLoadingTreeViews === 0) { + this.loadingWasHidden = true; this.statusBarItem.hide(); + this.statusBarItem.dispose(); } } diff --git a/src/types/telemetryEventNames.ts b/src/telemetry.ts similarity index 62% rename from src/types/telemetryEventNames.ts rename to src/telemetry.ts index 0bd17288..b87dacfc 100644 --- a/src/types/telemetryEventNames.ts +++ b/src/telemetry.ts @@ -1,4 +1,8 @@ -export const enum TelemetryError { +import TelemetryReporter from '@vscode/extension-telemetry'; +import { env, ExtensionContext, ExtensionMode } from 'vscode'; + + +export const enum TelemetryErrorEventNames { /** * Uncaught exception. Doesn't tell much. Need to see stack trace attached. */ @@ -22,7 +26,6 @@ export const enum TelemetryError { FAILED_TO_GET_PODS_OF_A_DEPLOYMENT = 'FAILED_TO_GET_PODS_OF_A_DEPLOYMENT', FAILED_TO_GET_KUSTOMIZATIONS = 'FAILED_TO_GET_KUSTOMIZATIONS', FAILED_TO_GET_HELM_RELEASES = 'FAILED_TO_GET_HELM_RELEASES', - FAILED_TO_GET_CANARIES = 'FAILED_TO_GET_CANARIES', FAILED_TO_GET_GIT_REPOSITORIES = 'FAILED_TO_GET_GIT_REPOSITORIES', FAILED_TO_GET_OCI_REPOSITORIES = 'FAILED_TO_GET_OCI_REPOSITORIES', FAILED_TO_GET_HELM_REPOSITORIES = 'FAILED_TO_GET_HELM_REPOSITORIES', @@ -62,9 +65,9 @@ export const enum TelemetryError { FAILED_TO_PARSE_GIT_TAGS_FROM_OUTPUT = 'FAILED_TO_PARSE_GIT_TAGS_FROM_OUTPUT', } -export type TelemetryErrorEvent = TelemetryError | string; +export type TelemetryErrorEvent = TelemetryErrorEventNames | string; -export const enum TelemetryEvent { +export const enum TelemetryEventNames { Startup = 'STARTUP', /** * First ever extension activation. @@ -94,3 +97,98 @@ export const enum TelemetryEvent { CreateWorkload = 'CREATE_WORKLOAD', DeleteWorkload = 'DELETE_WORKLOAD', } + +/** + * Map event names with the data type of payload sent + * When undefined - send only the event name. + */ +interface TelemetryEventNamePropertyMapping { + [TelemetryEventNames.Startup]: undefined; + [TelemetryEventNames.EnableGitOps]: { + clusterProvider: string; + }; + [TelemetryEventNames.DisableGitOps]: { + clusterProvider: string; + }; + [TelemetryEventNames.NewInstall]: undefined; + [TelemetryEventNames.CreateSourceOpenWebview]: undefined; + [TelemetryEventNames.CreateSource]: { + kind: string; + }; + [TelemetryEventNames.DeleteSource]: { + kind: string; + }; + [TelemetryEventNames.CreateWorkload]: { + kind: string; + }; + [TelemetryEventNames.DeleteWorkload]: { + kind: string; + }; +} + +export class Telemetry { + + private context: ExtensionContext; + private reporter: TelemetryReporter; + + constructor(context: ExtensionContext, extensionVersion: string, extensionId: string) { + this.context = context; + const key = '9a491deb-120a-4a6e-8893-f528d4f6bd9c'; + this.reporter = new TelemetryReporter(extensionId, extensionVersion, key); + } + + /** + * Check if it's allowed to send the telemetry. + */ + private canSend(): boolean { + // Don't send telemetry when developing or testing the extension + if (this.context.extensionMode !== ExtensionMode.Production) { + return false; + } + // Don't send telemetry when user disabled it in Settings + if (!env.isTelemetryEnabled) { + return false; + } + return true; + } + + /** + * Send custom events. + * + * @param eventName sent message title + * @param payload custom properties to add to the message + */ + send(eventName: E, payload?: T[E]): void { + if (!this.canSend()) { + return; + } + + // @ts-ignore + this.reporter.sendTelemetryEvent(eventName, payload); + } + + /** + * Send caught or uncaught errors. + * + * @param eventName sent message title + * @param error error object of the uncaught exception + */ + sendError(eventName: TelemetryErrorEvent, error?: Error): void { + if (!this.canSend()) { + return; + } + + if (!error) { + error = new Error(eventName); + } + + this.reporter.sendTelemetryException(error, { + name: eventName, + }); + + } + + dispose(): void { + this.reporter.dispose(); + } +} diff --git a/src/cli/shell/terminal.ts b/src/terminal.ts similarity index 92% rename from src/cli/shell/terminal.ts rename to src/terminal.ts index 3d63b3b8..c30a8cd5 100644 --- a/src/cli/shell/terminal.ts +++ b/src/terminal.ts @@ -1,6 +1,5 @@ import { Disposable, ExtensionContext, Terminal, window } from 'vscode'; - -import { extensionContext } from 'extension'; +import { getExtensionContext } from './extensionContext'; const terminalName = 'gitops'; @@ -54,7 +53,7 @@ export function runTerminalCommand( focusTerminal?: boolean; } = {}): void { - const terminal = getTerminal(extensionContext, cwd); + const terminal = getTerminal(getExtensionContext(), cwd); terminal.show(!focusTerminal); terminal.sendText(command, true); } diff --git a/src/test/runTest.ts b/src/test/runTest.ts index ead36fb1..dc1e8b6b 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -1,5 +1,9 @@ import * as path from 'path'; -import { runTests } from '@vscode/test-electron'; +import which from 'which'; +import * as shelljs from 'shelljs'; + + +import { runTests, downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from '@vscode/test-electron'; async function main() { try { diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index f454de9c..d8f2141e 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -1,10 +1,11 @@ import * as assert from 'assert'; -import { after, before } from 'mocha'; -import { ClusterDataProvider } from 'ui/treeviews/dataProviders/clusterDataProvider'; -import { SourceDataProvider } from 'ui/treeviews/dataProviders/sourceDataProvider'; -import { WorkloadDataProvider } from 'ui/treeviews/dataProviders/workloadDataProvider'; -import { NamespaceNode } from 'ui/treeviews/nodes/namespaceNode'; +import { before } from 'mocha'; import * as vscode from 'vscode'; +import { DataProvider } from '../../views/dataProviders/dataProvider'; +import { ClusterDataProvider } from '../../views/dataProviders/clusterDataProvider'; +import { SourceDataProvider } from '../../views/dataProviders/sourceDataProvider'; +import { WorkloadDataProvider } from '../../views/dataProviders/workloadDataProvider'; +import { NamespaceNode } from '../../views/nodes/namespaceNode'; let api: { shell: { execWithOutput: any; }; @@ -16,23 +17,12 @@ let api: { }; let currentContext: string; -let source; -let namespace; -let workload; async function getTreeItem(treeDataProvider: WorkloadDataProvider | SourceDataProvider | ClusterDataProvider, label: string): Promise { const childItems = await treeDataProvider.getChildren(); return childItems.find(i => i.label === label); } - -function retry(fn: ()=> Promise, retries = 3, err = null) { - if (!retries) { - return Promise.reject(err); - } - return fn().catch((erro): any => retry(fn, (retries - 1), erro)); -} - async function installKubernetesToolsDependency() { const name = 'ms-kubernetes-tools.vscode-kubernetes-tools'; let extension = vscode.extensions.getExtension(name); @@ -67,13 +57,6 @@ suite('Extension Test Suite', () => { assert.notStrictEqual(fluxNamespaceOutput.code, 0, 'Flux must not be installed - the namespace flux-system should not exist'); }); - // Flux should be uninstalled after tests are done - after(async function() { - this.timeout(4000); // default 2000ms is too brief, sometimes takes longer - let cluster = await getTreeItem(api.data.clusterTreeViewProvider, currentContext); - await vscode.commands.executeCommand('gitops.flux.uninstall', cluster); - }); - test('Extension is activated', async () => { const gitopsext = vscode.extensions.getExtension('weaveworks.vscode-gitops-tools'); @@ -95,34 +78,27 @@ suite('Extension Test Suite', () => { await vscode.commands.executeCommand('gitops.flux.install', cluster); - retry(async function () { - cluster = await getTreeItem(api.data.clusterTreeViewProvider, currentContext); - assert.strictEqual((cluster as any).children.length, 4, 'Enabling GitOps should list installed Flux controllers'); - }); + cluster = await getTreeItem(api.data.clusterTreeViewProvider, currentContext); + assert.strictEqual((cluster as any).children.length, 4, 'Enabling GitOps should list installed Flux controllers'); }); + test('Sources are listed', async function() { this.timeout(15000); await api.shell.execWithOutput('flux create source git podinfo --url=https://github.com/stefanprodan/podinfo --branch master --namespace default'); await vscode.commands.executeCommand('gitops.views.refreshResourcesTreeView'); - retry(async function () { - - namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; - source = namespace && namespace.children[0]; - assert.strictEqual(source?.label, 'GitRepository: podinfo', 'Adding a GitSource and refreshing the view should list it'); - }); + let namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; + let source = namespace && namespace.children[0]; + assert.strictEqual(source?.label, 'GitRepository: podinfo', 'Adding a GitSource and refreshing the view should list it'); await api.shell.execWithOutput('flux delete source git podinfo -s --namespace default'); await vscode.commands.executeCommand('gitops.views.refreshAllTreeViews'); // refresh all' - retry(async function () { - - namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; - source = namespace && namespace.children[0]; - assert.strictEqual(source, undefined, 'Removing a GitSource and refreshing all views should unlist it'); - }); + namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; + source = namespace && namespace.children[0]; + assert.strictEqual(source, undefined, 'Removing a GitSource and refreshing all views should unlist it'); }); test('OCI Sources are listed', async function() { @@ -131,41 +107,35 @@ suite('Extension Test Suite', () => { await api.shell.execWithOutput('flux create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag-semver 6.1.x --namespace default'); await vscode.commands.executeCommand('gitops.views.refreshResourcesTreeView'); - retry(async function () { - namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; - source = namespace && namespace.children[0]; - assert.strictEqual(source?.label, 'OCIRepository: podinfo', 'Adding a OCI Source and refreshing the view should list it'); - }); + let namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; + let source = namespace && namespace.children[0]; + assert.strictEqual(source?.label, 'OCIRepository: podinfo', 'Adding a OCI Source and refreshing the view should list it'); await api.shell.execWithOutput('flux delete source oci podinfo -s --namespace default'); await vscode.commands.executeCommand('gitops.views.refreshAllTreeViews'); // refresh all' - retry(async function () { - namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; - source = namespace && namespace.children[0]; - assert.strictEqual(source, undefined, 'Removing an OCI Source and refreshing all views should unlist it'); - }); + namespace = await getTreeItem(api.data.sourceTreeViewProvider, 'default') as NamespaceNode; + source = namespace && namespace.children[0]; + assert.strictEqual(source, undefined, 'Removing an OCI Source and refreshing all views should unlist it'); }); + test('Kustomizations are listed', async function() { - this.timeout(20000); + this.timeout(10000); await api.shell.execWithOutput('flux create kustomization podinfo --target-namespace=default --source=podinfo --path="./kustomize" --timeout=5s --namespace=default'); await vscode.commands.executeCommand('gitops.views.refreshResourcesTreeView'); - retry(async function () { - namespace = await getTreeItem(api.data.workloadTreeViewProvider, 'default') as NamespaceNode; - workload = namespace && namespace.children[0]; - assert.strictEqual(workload?.label, 'Kustomization: podinfo', 'Adding a Kustomization and refreshing the view should list it'); - }); + let namespace = await getTreeItem(api.data.workloadTreeViewProvider, 'default') as NamespaceNode; + let workload = namespace && namespace.children[0]; + assert.strictEqual(workload?.label, 'Kustomization: podinfo', 'Adding a Kustomization and refreshing the view should list it'); + await api.shell.execWithOutput('flux delete kustomization podinfo -s --namespace=default'); await vscode.commands.executeCommand('gitops.views.refreshAllTreeViews'); // refresh all' - retry(async function () { - namespace = await getTreeItem(api.data.workloadTreeViewProvider, 'default') as NamespaceNode; - workload = namespace && namespace.children[0]; - assert.strictEqual(workload, undefined, 'Removing a Kustomization and refreshing all views should unlist it'); - }); + namespace = await getTreeItem(api.data.workloadTreeViewProvider, 'default') as NamespaceNode; + workload = namespace && namespace.children[0]; + assert.strictEqual(workload, undefined, 'Removing a Kustomization and refreshing all views should unlist it'); }); test('Disable GitOps uninstalls Flux', async function () { @@ -175,10 +145,8 @@ suite('Extension Test Suite', () => { await vscode.commands.executeCommand('gitops.flux.uninstall', cluster); - retry(async function () { - cluster = await getTreeItem(api.data.clusterTreeViewProvider, currentContext); - assert.strictEqual((cluster as any).children.length, 0, 'Disabling GitOps should list 0 installed Flux controllers'); - }); + cluster = await getTreeItem(api.data.clusterTreeViewProvider, currentContext); + assert.strictEqual((cluster as any).children.length, 0, 'Enabling GitOps should list installed Flux controllers'); }); }); diff --git a/src/types/extensionIds.ts b/src/types/extensionIds.ts deleted file mode 100644 index a19279e2..00000000 --- a/src/types/extensionIds.ts +++ /dev/null @@ -1,101 +0,0 @@ -export const enum GitOpsExtensionConstants { - ExtensionId = 'weaveworks.vscode-gitops-tools', -} - -/** - * GitOps view ids. - */ -export const enum TreeViewId { - ClustersView = 'gitops.views.clusters', - SourcesView = 'gitops.views.sources', - WorkloadsView = 'gitops.views.workloads', - WgeView = 'gitops.views.wge', - DocumentationView = 'gitops.views.documentation', -} - -export const enum CommandId { - // vscode commands - /** - * Opens the provided resource in the editor. Can be a text or binary file, or an http(s) URL. - */ - VSCodeOpen = 'vscode.open', - VSCodeReload = 'workbench.action.reloadWindow', - /** - * Set vscode context to use in keybindings/menus/welcome views - * @see https://code.visualstudio.com/api/references/when-clause-contexts - */ - VSCodeSetContext = 'setContext', - - // kubectl - SetCurrentKubernetesContext = 'gitops.kubectl.setCurrentContext', - - // flux - Suspend = 'gitops.suspend', - Resume = 'gitops.resume', - FluxCheck = 'gitops.flux.check', - FluxCheckPrerequisites = 'gitops.flux.checkPrerequisites', - FluxEnableGitOps = 'gitops.flux.install', - FluxDisableGitOps = 'gitops.flux.uninstall', - FluxReconcileSource = 'gitops.flux.reconcileSource', - FluxReconcileRepository = 'gitops.flux.reconcileRepository', - FluxReconcileWorkload = 'gitops.flux.reconcileWorkload', - FluxReconcileWorkloadWithSource = 'gitops.flux.reconcileWorkloadWithSource', - FluxTrace = 'gitops.flux.trace', - - // tree view - SetClusterProvider = 'gitops.setClusterProvider', - RefreshAllTreeViews = 'gitops.views.refreshAllTreeViews', - RefreshResourcesTreeView = 'gitops.views.refreshResourcesTreeView', - PullGitRepository = 'gitops.views.pullGitRepository', - CreateGitRepository = 'gitops.views.createGitRepository', - ExpandAllSources = 'gitops.views.expandAllSources', - ExpandAllWorkloads = 'gitops.views.expandAllWorkloads', - - CreateKustomization = 'gitops.createKustomization', - ShowWorkloadsHelpMessage = 'gitops.views.showWorkloadsHelpMessage', - DeleteWorkload = 'gitops.views.deleteWorkload', - DeleteSource = 'gitops.views.deleteSource', - CopyResourceName = 'gitops.copyResourceName', - AddSource = 'gitops.addSource', - AddKustomization = 'gitops.addKustomization', - - KubectlApplyPath = 'gitops.kubectlApplyPath', - KubectlDeletePath = 'gitops.kubectlDeletePath', - KubectlApplyKustomization = 'gitops.kubectlApplyKustomization', - - - // editor - EditorOpenResource = 'gitops.editor.openResource', - EditorOpenKubeconfig = 'gitops.editor.openKubeconfig', - - // webview - ShowLogs = 'gitops.editor.showLogs', - ShowNewUserGuide = 'gitops.views.showNewUserGuide', - - // output commands - ShowOutputChannel = 'gitops.output.show', - - // others - ShowInstalledVersions = 'gitops.showInstalledVersions', - InstallFluxCli = 'gitops.installFluxCli', - ShowGlobalState = 'gitops.dev.showGlobalState', - - // wge - OpenInWgePortal = 'gitops.views.openInWgePortal', - CreateFromTemplate = 'gitops.views.createFromTemplate', - EnableAutoPromotion = 'gitops.autoPromotion', - DisableAutoPromotion = 'gitops.manualPromotion', - SetContextToGitopsCluster = 'gitops.views.setContextToGitopsCluster', -} - - -/** - * GitOps context types. - */ -export const enum ContextId { - CurrentClusterGitOpsNotEnabled = 'gitops:currentClusterGitOpsNotEnabled', - ClusterUnreachable = 'gitops:clusterUnreachable', - - IsDev = 'gitops:isDev', - IsWGE = 'gitops:isWGE', -} diff --git a/src/types/flux/artifact.ts b/src/types/flux/artifact.ts deleted file mode 100644 index ed3e3c37..00000000 --- a/src/types/flux/artifact.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Artifact represents the output of a source synchronisation. - */ - -export interface Artifact { - - /** - * Path is the relative file path of this artifact - */ - readonly path: string; - - /** - * URL is the HTTP address of this artifact - */ - readonly url: string; - - /** - * Revision is a human readable identifier traceable in the origin source system. - * It can be a Git commit SHA, Git tag, a Helm index timestamp, - * a Helm chart version, etc. - */ - readonly revision?: string; - - /** - * Checksum is the SHA1 checksum of the artifact - */ - readonly checksum?: string; - - /** - * LastUpdateTime is the timestamp corresponding to the last update of this artifact - */ - readonly lastUpdateTime: string; -} diff --git a/src/types/flux/canary.ts b/src/types/flux/canary.ts deleted file mode 100644 index 2881ad40..00000000 --- a/src/types/flux/canary.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { Condition, Kind, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; - -export interface Canary extends KubernetesObject { - readonly kind: Kind.Canary; - readonly spec: CanarySpec; - readonly status: CanaryStatus; -} - -declare interface CanarySpec { - provider?: string; - metricsServer?: string; - targetRef: LocalObjectReference; - autoscalerRef?: AutoscalerRefernce; - ingressRef?: LocalObjectReference; - routeRef?: LocalObjectReference; - upstreamRef?: CrossNamespaceObjectReference; - service: CanaryService; - analysis?: CanaryAnalysis; - canaryAnalysis?: CanaryAnalysis; - progressDeadlineSeconds?: number; - skipAnalysis?: boolean; - revertOnDeletion?: boolean; - suspend?: boolean; -} - -declare interface CanaryService { - name?: string; - port: number; - portName?: string; - targetPort?: number | string; - appProtocol?: string; - portDiscovery: boolean; - timeout?: string; - gateways?: string[]; - gatewayRefs?: any[]; - hosts?: string[]; - delegation?: boolean; - trafficPolicy?: any; - match?: any; - rewrite?: any; - retries?: any; - headers?: any; - mirror?: any[]; - corsPolicy?: any; - meshName?: string; - backends?: string[]; - apex?: CustomMetadata; - primary?: CustomMetadata; - canary?: CustomMetadata; -} - -declare interface CanaryAnalysis { - interval: string; - iterations?: number; - mirror?: boolean; - mirrorWeight?: number; - maxWeight?: number; - stepWeight?: number; - stepWeights?: number[]; - stepWeightPromotion?: number; - threshold: number; - primaryReadyThreshold?: number; - canaryReadyThreshold?: number; - alerts?: CanaryAlert[]; - metrics?: CanaryMetric[]; - webhooks?: CanaryWebhook[]; - match?: any[]; - sessionAffinity?: SessionAffinity; -} - -declare interface SessionAffinity { - cookieName?: string; - maxAge?: number; -} - -declare interface CanaryMetric { - name: string; - interval?: string; - threshold?: number; - thresholdRange?: CanaryThresholdRange; - query?: string; - templateRef?: CrossNamespaceObjectReference; - templateVariables?: { [key: string]: string;}; -} - -declare interface CanaryThresholdRange { - min?: number; - max?: number; -} - -declare interface CanaryAlert { - name: string; - severity?: AlertSeverity; - providerRef: CrossNamespaceObjectReference; -} - -declare interface CanaryWebhook { - type: HookType; - name: string; - url: string; - muteAlert: boolean; - timeout?: string; - metadata?: { [key: string]: string;}; -} - -declare interface CanaryWebhookPayload { - name: string; - namespace: string; - phase: CanaryPhase; - checksum: string; - metadata?: { [key: string]: string;}; -} - -declare interface CrossNamespaceObjectReference { - apiVersion?: string; - kind?: string; - name: string; - namespace?: string; -} - -declare interface LocalObjectReference { - apiVersion?: string; - kind?: string; - name: string; -} - -declare interface AutoscalerRefernce { - apiVersion?: string; - kind?: string; - name: string; - primaryScalerQueries: { [key: string]: string;}; - primaryScalerReplicas?: ScalerReplicas; -} - -declare interface ScalerReplicas { - minReplicas?: number; - maxReplicas?: number; -} - -declare interface CustomMetadata { - labels?: { [key: string]: string;}; - annotations?: { [key: string]: string;}; -} - -declare interface HTTPRewrite { - uri?: string; - authority?: string; - type?: string; -} - -// CanaryStatus is used for state persistence (read-only) -interface CanaryStatus { - phase: CanaryPhase; - failedChecks: number; - canaryWeight: number; - iterations: number; - - previousSessionAffinityCookie?: string; - sessionAffinityCookie?: string; - trackedConfigs?: any; - - lastAppliedSpec?: string; - lastPromotedSpec?: string; - lastTransitionTime: string; - conditions?: CanaryCondition[]; -} - -export type CanaryCondition = Required & { - // Type of this condition - type: 'Promoted'; -}; - - -export const enum CanaryPhase { - // Initializing means the canary initializing is underway - Initializing = 'Initializing', - // Initialized means the primary deployment, hpa and ClusterIP services - // have been created along with the service mesh or ingress objects - Initialized = 'Initialized', - // Waiting means the canary rollout is paused (waiting for confirmation to proceed) - Waiting = 'Waiting', - // Progressing means the canary analysis is underway - Progressing = 'Progressing', - // WaitingPromotion means the canary promotion is paused (waiting for confirmation to proceed) - WaitingPromotion = 'WaitingPromotion', - // Promoting means the canary analysis is finished and the primary spec has been updated - Promoting = 'Promoting', - // Finalising means the canary promotion is finished and traffic has been routed back to primary - Finalising = 'Finalising', - // Succeeded means the canary analysis has been successful - // and the canary deployment has been promoted - Succeeded = 'Succeeded', - // Failed means the canary analysis failed - // and the canary deployment has been scaled to zero - Failed = 'Failed', - // Terminating means the canary has been marked - // for deletion and in the finalizing state - Terminating = 'Terminating', - // Terminated means the canary has been finalized - // and successfully deleted - Terminated = 'Terminated', -} - - -// // HookType can be pre, post or during rollout -export enum HookType { - // RolloutHook execute webhook during the canary analysis - RolloutHook = 'rollout', - // PreRolloutHook execute webhook before routing traffic to canary - PreRolloutHook = 'pre-rollout', - // PostRolloutHook execute webhook after the canary analysis - PostRolloutHook = 'post-rollout', - // ConfirmRolloutHook halt canary analysis until webhook returns HTTP 200 - ConfirmRolloutHook = 'confirm-rollout', - // ConfirmPromotionHook halt canary promotion until webhook returns HTTP 200 - ConfirmPromotionHook = 'confirm-promotion', - // EventHook dispatches Flagger events to the specified endpoint - EventHook = 'event', - // RollbackHook rollback canary analysis if webhook returns HTTP 200 - RollbackHook = 'rollback', - // ConfirmTrafficIncreaseHook increases traffic weight if webhook returns HTTP 200 - ConfirmTrafficIncreaseHook = 'confirm-traffic-increase', -} - - -export enum AlertSeverity { - SeverityInfo = 'info', - SeverityWarn = 'warn', - SeverityError = 'error', -} - diff --git a/src/types/flux/gitOpsCluster.ts b/src/types/flux/gitOpsCluster.ts deleted file mode 100644 index 621e1407..00000000 --- a/src/types/flux/gitOpsCluster.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Kind, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; - -export interface GitOpsCluster extends KubernetesObject { - readonly kind: Kind.GitopsCluster; - readonly spec: GitOpsClusterSpec; - readonly status: GitOpsClusterStatus; -} - -export type GitOpsClusterSpec = any; -export type GitOpsClusterStatus = any; - - - diff --git a/src/types/flux/gitOpsTemplate.ts b/src/types/flux/gitOpsTemplate.ts deleted file mode 100644 index f412614c..00000000 --- a/src/types/flux/gitOpsTemplate.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Kind, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; - - - -export interface GitOpsTemplate extends KubernetesObject { - readonly kind: Kind.GitOpsTemplate; - - readonly spec: { - readonly params?: TemplateParam[]; - readonly description?: string; - }; - - readonly status?: any; -} - -/** - * params spec for GitOpsTempalte - */ -export interface TemplateParam { - readonly name: string; - readonly description: string; - readonly options?: string[]; - readonly default?: string; - readonly required?: boolean; -} - - diff --git a/src/types/flux/gitopsset.ts b/src/types/flux/gitopsset.ts deleted file mode 100644 index cca46d98..00000000 --- a/src/types/flux/gitopsset.ts +++ /dev/null @@ -1,133 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { Condition, Kind, KubernetesObject, LocalObjectReference } from 'types/kubernetes/kubernetesTypes'; - - -export interface GitOpsSet extends KubernetesObject { - readonly kind: Kind.GitOpsSet; - readonly spec: GitOpsSetSpec; - readonly status: GitOpsSetStatus; -} - -declare interface GitOpsSetSpec { - suspend?: boolean; - generators?: GitOpsSetGenerator[]; - templates?: GitOpsSetTemplate[]; - serviceAccountName?: string; -} - -declare interface GitOpsSetTemplate { - repeat?: string; - content: any; -} - -declare interface ClusterGenerator { - selector?: k8s.V1LabelSelector; -} - -declare interface ConfigGenerator { - kind: string; - name: string; -} - -declare interface ListGenerator { - elements?: any[]; -} - -declare interface PullRequestGenerator { - interval: any; - driver: string; - serverURL?: string; - repo: string; - secretRef?: LocalObjectReference; - labels?: string[]; - forks?: boolean; -} - -declare interface APIClientGenerator { - interval: any; - endpoint?: string; - method?: string; - jsonPath?: string; - headersRef?: HeadersReference; - body?: any; - singleElement?: boolean; - secretRef?: LocalObjectReference; -} - -declare interface HeadersReference { - kind: string; - name: string; -} - -declare interface RepositoryGeneratorFileItem { - path: string; -} - -declare interface RepositoryGeneratorDirectoryItem { - path: string; - exclude?: boolean; -} - -declare interface GitRepositoryGenerator { - repositoryRef?: string; - files?: RepositoryGeneratorFileItem[]; - directories?: RepositoryGeneratorDirectoryItem[]; -} - -declare interface OCIRepositoryGenerator { - repositoryRef?: string; - files?: RepositoryGeneratorFileItem[]; - directories?: RepositoryGeneratorDirectoryItem[]; -} - -declare interface MatrixGenerator { - generators?: GitOpsSetNestedGenerator[]; - singleElement?: boolean; -} - -declare interface GitOpsSetNestedGenerator { - name?: string; - list?: ListGenerator; - gitRepository?: GitRepositoryGenerator; - ociRepository?: OCIRepositoryGenerator; - pullRequests?: PullRequestGenerator; - cluster?: ClusterGenerator; - apiClient?: APIClientGenerator; - imagePolicy?: ImagePolicyGenerator; - config?: ConfigGenerator; -} - -declare interface ImagePolicyGenerator { - policyRef?: string; -} - -declare interface GitOpsSetGenerator { - list?: ListGenerator; - pullRequests?: PullRequestGenerator; - gitRepository?: GitRepositoryGenerator; - ociRepository?: OCIRepositoryGenerator; - matrix?: MatrixGenerator; - cluster?: ClusterGenerator; - apiClient?: APIClientGenerator; - imagePolicy?: ImagePolicyGenerator; - config?: ConfigGenerator; -} - -declare interface GitOpsSetStatus { - observedGeneration?: number; - conditions?: Condition[]; - inventory?: ResourceInventory; -} - - -declare interface ResourceInventory { - entries: ResourceRef[]; -} - -declare interface ResourceRef { - // ID is the string representation of the Kubernetes resource object’s metadata, - // in the format ‘namespace_name_group_kind’. - id: string; - // Version is the API version of the Kubernetes resource object’s kind. - v: string; -} diff --git a/src/types/flux/object.ts b/src/types/flux/object.ts deleted file mode 100644 index eccbd653..00000000 --- a/src/types/flux/object.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { Bucket } from './bucket'; -import { Canary } from './canary'; -import { GitRepository } from './gitRepository'; -import { GitOpsSet } from './gitopsset'; -import { HelmRelease } from './helmRelease'; -import { HelmRepository } from './helmRepository'; -import { Kustomization } from './kustomization'; -import { OCIRepository } from './ociRepository'; -import { Pipeline } from './pipeline'; - -export type FluxSourceObject = GitRepository | OCIRepository | HelmRepository | Bucket; -export type FluxWorkloadObject = Kustomization | HelmRelease; -export type ToolkitObject = FluxSourceObject | FluxWorkloadObject | GitOpsSet | Pipeline | Canary; - -export const FluxSourceKinds: Kind[] = [ - Kind.GitRepository, - Kind.OCIRepository, - Kind.HelmRepository, - Kind.Bucket, -]; - -export const FluxWorkloadKinds: Kind[] = [ - Kind.Kustomization, - Kind.HelmRelease, - Kind.Canary, -]; - diff --git a/src/types/flux/pipeline.ts b/src/types/flux/pipeline.ts deleted file mode 100644 index b86b5aa8..00000000 --- a/src/types/flux/pipeline.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Condition, Kind, KubernetesObject, LocalObjectReference } from 'types/kubernetes/kubernetesTypes'; - -export interface Pipeline extends KubernetesObject { - readonly kind: Kind.Pipeline; - readonly spec: PipelineSpec; - readonly status: PipelineStatus; -} - -declare interface PipelineSpec { - environments: PipelineEnvironment[]; - appRef: LocalAppReference; - promotion?: Promotion; -} - -declare interface Promotion { - manual?: boolean; - strategy: Strategy; -} - -declare interface Strategy { - pullRequest?: PullRequestPromotion; - notification?: NotificationPromotion; - secretRef?: LocalObjectReference; -} - -declare interface PullRequestPromotion { - type: GitProviderType; - url: string; - baseBranch: string; - secretRef: LocalObjectReference; -} - - -export enum GitProviderType { - Github = 'github', - Gitlab = 'gitlab', - BitBucketServer = 'bitbucket-server', - AzureDevOps = 'azure-devops', -} - -type NotificationPromotion = unknown; - -export declare interface PipelineStatus { - observedGeneration?: number; - conditions?: Condition[]; - environments: { [key: string]: EnvironmentStatus | undefined;}; -} - -export declare interface EnvironmentStatus { - waitingApproval?: WaitingApproval; - targets?: TargetStatus[]; -} - -declare interface WaitingApproval { - revision: string; -} - -export declare interface ClusterAppReference { - clusterRef?: CrossNamespaceClusterReference; -} - -export declare interface TargetStatus { - clusterAppRef: ClusterAppReference; - ready: boolean; - revision?: string; - error?: string; -} - -export declare interface PipelineEnvironment { - name: string; - targets: PipelineTarget[]; - promotion?: Promotion; -} - -export declare interface PipelineTarget { - namespace: string; - clusterRef?: CrossNamespaceClusterReference; -} - -export declare interface LocalAppReference { - apiVersion: string; - kind: string; - name: string; -} - -export declare interface CrossNamespaceClusterReference { - apiVersion?: string; - kind: string; - name: string; - namespace?: string; -} - - -export enum PipelineReasons { - // Reasons used by the original controller. - // TargetClusterNotFoundReason signals a failure to locate a cluster resource on the management cluster. - TargetClusterNotFoundReason = 'TargetClusterNotFound', - // TargetClusterNotReadyReason signals that a cluster pointed to by a Pipeline is not ready. - TargetClusterNotReadyReason = 'TargetClusterNotReady', - // ReconciliationSucceededReason signals that a Pipeline has been successfully reconciled. - ReconciliationSucceededReason = 'ReconciliationSucceeded', - - // Reasons used by the level-triggered controller. - // TargetNotReadableReason signals that an app object pointed to by a Pipeline cannot be read, either because it is not found, or it's on a cluster that cannot be reached. - TargetNotReadableReason = 'TargetNotReadable', -} - - - diff --git a/src/types/kubernetes/clusterProvider.ts b/src/types/kubernetes/clusterProvider.ts deleted file mode 100644 index 5ac9517e..00000000 --- a/src/types/kubernetes/clusterProvider.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Cluster providers will have some differences. - * For example, AKS cluster has special handling - * for enabling GitOps. - */ - -export const enum ClusterProvider { - /** - * Azure Kubernetes Service. - */ - AKS = 'AKS', - /** - * Cluster managed by Azure Arc. - */ - AzureARC = 'Azure Arc', - /** - * Any cluster that is not AKS and not Azure Arc is - * considered generic at this point. - */ - Generic = 'Generic', - /** - * Not loaded yet. - */ - Unknown = 'Unknown', - /** - * Error occurred when trying to determine the cluster provider - */ - DetectionFailed = 'DetectionFailed', -} - -export type KnownClusterProviders = Exclude, ClusterProvider.DetectionFailed>; -export const knownClusterProviders: KnownClusterProviders[] = [ - ClusterProvider.AKS, - ClusterProvider.AzureARC, - ClusterProvider.Generic, -]; - -export interface ClusterInfo { - clusterProvider: ClusterProvider; - isClusterProviderUserOverride: boolean; - isAzure: boolean; -} diff --git a/src/types/kubernetes/kubernetesTypes.ts b/src/types/kubernetes/kubernetesTypes.ts deleted file mode 100644 index 482d8695..00000000 --- a/src/types/kubernetes/kubernetesTypes.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; - -export type KubernetesListObject = k8s.KubernetesListObject; - -export type Metadata = k8s.V1ObjectMeta & { - // required - name: string; - uid: string; -}; - -export type KubernetesObject = k8s.KubernetesObject & { - spec?: unknown; - status?: unknown; - // required - kind: string; - metadata: Metadata; -}; - -export type Condition = k8s.V1Condition; - -// Specify types from `@kubernetes/client-node` -export type Namespace = Required & { - readonly kind: Kind.Namespace; - metadata: Metadata; -}; - -export type Deployment = Required & { - readonly kind: Kind.Deployment; - metadata: Metadata; -}; - -export type ConfigMap = Required & { - readonly kind: Kind.ConfigMap; - metadata: Metadata; -}; - -export type Node = Required & { - readonly kind: Kind.Node; - metadata: Metadata; -}; - -export type Pod = Required & { - readonly kind: Kind.Pod; - metadata: Metadata; -}; - -/** - * Defines supported Kubernetes object kinds. - */ -export const enum Kind { - Bucket = 'Bucket', - GitRepository = 'GitRepository', - OCIRepository = 'OCIRepository', - HelmRepository = 'HelmRepository', - HelmRelease = 'HelmRelease', - Kustomization = 'Kustomization', - GitOpsTemplate = 'GitOpsTemplate', - Canary = 'Canary', - Pipeline = 'Pipeline', - GitOpsSet = 'GitOpsSet', - GitopsCluster = 'GitopsCluster', - - Namespace = 'Namespace', - Deployment = 'Deployment', - Node = 'Node', - Pod = 'Pod', - - ConfigMap = 'ConfigMap', -} - - -const fullKinds: Record = { - Bucket: 'Buckets.source.toolkit.fluxcd.io', - GitRepository: 'GitRepositories.source.toolkit.fluxcd.io', - OCIRepository: 'OCIRepositories.source.toolkit.fluxcd.io', - HelmRepository: 'HelmRepositories.source.toolkit.fluxcd.io', - HelmRelease: 'HelmReleases.helm.toolkit.fluxcd.io', - Kustomization: 'Kustomizations.kustomize.toolkit.fluxcd.io', - - GitOpsTemplate: 'GitOpsTemplates.templates.weave.works', - Canary: 'Canaries.flagger.app', - Pipeline: 'Pipelines.pipelines.weave.works', - GitOpsSet: 'GitOpsSets.templates.weave.works', - GitOpsCluster: 'GitOpsClusters.gitops.weave.works', -}; - -export function qualifyToolkitKind(kind: string): string { - return fullKinds[kind] || kind; -} - - -/* - * LocalObjectReference contains enough information - * to let you locate the referenced object inside the same namespace. - */ -export interface LocalObjectReference { - - /** - * Name of the referent. - * @see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - */ - name: string; -} - -/** - * JSON represents any valid JSON value. These types are supported: - * bool, int64, float64, string, []interface{}, map[string]interface{} and nil. - */ -export interface KubernetesJSON { - [key: string]: unknown; -} diff --git a/src/types/showLogsTypes.ts b/src/types/showLogsTypes.ts deleted file mode 100644 index 16b3f6b4..00000000 --- a/src/types/showLogsTypes.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { QuickPickItem, Uri } from 'vscode'; - -import { V1ObjectMeta } from '@kubernetes/client-node'; - -export class ResourceKind implements QuickPickItem { - constructor(readonly displayName: string, readonly pluralDisplayName: string, - readonly manifestKind: string, readonly abbreviation: string, readonly apiName?: string) { - } - - get label() { - return this.displayName; - } - get description() { - return ''; - } -} - -export interface ResourceNode { - readonly nodeType: 'resource'; - readonly name?: string; - readonly namespace?: string; - readonly kindName: string; - readonly metadata: V1ObjectMeta; - readonly kind: ResourceKind; - uri(outputFormat: string): Uri; -} - -export const podResourceKind = new ResourceKind('Pod', 'Pods', 'Pod', 'pod', 'pods'); diff --git a/src/ui/icons.ts b/src/ui/icons.ts deleted file mode 100644 index cc6f83ad..00000000 --- a/src/ui/icons.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ThemeColor, ThemeIcon } from 'vscode'; - -export const enum CommonIcon { - Error = 'error', - Warning = 'warning', - Success = 'success', - Disconnected = 'disconnected', - Progressing = 'progressing', - Loading = 'loading', - Unknown = 'unknown', -} - -export const IconColors: Record = { - 'error': 'editorError.foreground', - 'warning': 'editorWarning.foreground', - 'pass': 'terminal.ansiGreen', - 'green': 'terminal.ansiGreen', - 'foreground': 'foreground', -}; - -const IconDefinitions: Record = { - [CommonIcon.Error]: ['error', 'editorError.foreground'], - [CommonIcon.Warning]: ['warning', 'editorWarning.foreground'], - [CommonIcon.Success]: ['pass', 'terminal.ansiGreen'], - [CommonIcon.Disconnected]: ['sync-ignored', 'editorError.foreground'], - [CommonIcon.Progressing]: ['sync~spin', 'terminal.ansiGreen'], - [CommonIcon.Loading]: ['loading~spin', 'foreground'], - [CommonIcon.Unknown]: ['circle-large-outline', 'foreground'], -}; - -export function commonIcon(icon: CommonIcon) { - const [id, color] = IconDefinitions[icon]; - return new ThemeIcon(id, new ThemeColor(color)); -} - - -export function themeIcon(icon: string, color?: string) { - color = color ?? 'foreground'; - color = IconColors[color] ?? color; - return new ThemeIcon(icon, new ThemeColor(color)); -} - diff --git a/src/ui/promptToInstallFlux.ts b/src/ui/promptToInstallFlux.ts deleted file mode 100644 index fe8ca867..00000000 --- a/src/ui/promptToInstallFlux.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { window } from 'vscode'; - -import { getFluxVersion } from 'cli/checkVersions'; -import { installFluxCli } from 'commands/installFluxCli'; -import { Errorable, failed } from 'types/errorable'; - -/** - * Show notification with button to install flux - * (only when flux was not found). - */ - -export async function checkInstalledFluxVersion(): Promise> { - const version = await getFluxVersion(); - if (failed(version)) { - showInstallFluxNotification(); - return { - succeeded: false, - error: ['Flux not found'], - }; - } else { - return { - succeeded: true, - result: null, - }; - } -} - -async function showInstallFluxNotification() { - const installButton = 'Install Flux'; - const pressedButton = await window.showErrorMessage('Please install flux CLI to use GitOps Tools.', installButton); - if (pressedButton === installButton) { - installFluxCli(); - } -} diff --git a/src/ui/treeviews/dataProviders/asyncDataProvider.ts b/src/ui/treeviews/dataProviders/asyncDataProvider.ts deleted file mode 100644 index 07011238..00000000 --- a/src/ui/treeviews/dataProviders/asyncDataProvider.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ApiState } from 'cli/kubernetes/apiResources'; -import { KubeConfigState, kubeConfigState } from 'cli/kubernetes/kubernetesConfig'; -import { ContextData, ViewData, currentContextData } from 'data/contextData'; -import { InfoLabel, infoNodes } from 'utils/makeTreeviewInfoNode'; -import { NamespaceNode } from '../nodes/namespaceNode'; -import { TreeNode } from '../nodes/treeNode'; -import { clusterDataProvider } from '../treeViews'; -import { SimpleDataProvider } from './simpleDataProvider'; - -/**` - * Defines tree view data provider base class for all GitOps tree views. - */ -export class AsyncDataProvider extends SimpleDataProvider { - get nodes() { - return this.viewData(currentContextData()).nodes; - } - - // child views override this to provide their own view data - protected viewData(contextData: ContextData) { - return new ViewData(); - } - - public currentViewData() { - return this.viewData(currentContextData()); - } - - - // give nodes for vscode to render based on async data loading state - protected async getRootNodes(): Promise { - const context = currentContextData(); - - if(context.apiState === ApiState.Loading) { - return infoNodes(InfoLabel.LoadingApi, this); - } - - if(context.apiState === ApiState.ClusterUnreachable) { - return infoNodes(InfoLabel.ClusterUnreachable, this); - } - - // return empty array so that vscode welcome view with embedded link "Enable Gitops ..." is shown - if(clusterDataProvider.currentContextIsGitOpsNotEnabled()) { - return []; - } - - if (this.currentViewData().loading || kubeConfigState === KubeConfigState.Loading) { - return infoNodes(InfoLabel.Loading, this); - } - - if(this.currentViewData().nodes.length === 0) { - return infoNodes(InfoLabel.NoResources, this); - } - - return this.currentViewData().nodes; - } - - - public async reload() { - const context = currentContextData(); - const viewData = this.viewData(context); - - - if(viewData.loading) { - return; - } - - viewData.loading = true; - viewData.saveCollapsibleStates(); - if(viewData.nodes.length === 0) { - // show Loading... if no nodes yet - this.redraw(); - } - viewData.nodes = []; // clear them first for good luck - viewData.nodes = await this.loadRootNodes(); - viewData.loadCollapsibleStates(); - viewData.loading = false; - - this.nodes.forEach(node => { - if(node instanceof NamespaceNode) { - node.updateLabel(); - } - }); - this.redraw(); - } -} diff --git a/src/ui/treeviews/dataProviders/clusterDataProvider.ts b/src/ui/treeviews/dataProviders/clusterDataProvider.ts deleted file mode 100644 index 96cf6e00..00000000 --- a/src/ui/treeviews/dataProviders/clusterDataProvider.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { statusBar } from 'ui/statusBar'; -import { ClusterNode } from '../nodes/cluster/clusterNode'; -import { SimpleDataProvider } from './simpleDataProvider'; - -/** - * Defines Clusters data provider for loading configured kubernetes clusters - * and contexts in GitOps Clusters tree view. - */ -export class ClusterDataProvider extends SimpleDataProvider { - - public getCurrentClusterNode(): ClusterNode | undefined { - const nodes = this.nodes as ClusterNode[]; - return nodes.find(c => c.context.name === kubeConfig?.getCurrentContext()); - } - - public redrawCurrentNode() { - this.redraw(this.getCurrentClusterNode()); - } - - - /** - * Creates Clusters tree view items from local kubernetes config. - */ - async loadRootNodes() { - statusBar.startLoadingTree(); - const clusterNodes: ClusterNode[] = []; - - let currentContextTreeItem: ClusterNode | undefined; - - if (kubeConfig.getContexts().length === 0) { - statusBar.stopLoadingTree(); - return []; - } - - for (const context of kubeConfig.getContexts()) { - const clusterNode = new ClusterNode(context); - if (context.name === kubeConfig.getCurrentContext()) { - currentContextTreeItem = clusterNode; - clusterNode.makeCollapsible(); - } - clusterNodes.push(clusterNode); - } - - statusBar.stopLoadingTree(); - - return clusterNodes; - } - - public updateCurrentContextChildNodes() { - const currentContextTreeItem = this.getCurrentClusterNode(); - currentContextTreeItem?.updateNodeChildren(); - } - - public currentContextIsGitOpsNotEnabled() { - const node = this.getCurrentClusterNode(); - // undefined is not false - if(node && typeof node.isGitOpsEnabled === 'boolean') { - return !node.isGitOpsEnabled; - } - return false; - } -} diff --git a/src/ui/treeviews/dataProviders/kubernetesObjectDataProvider.ts b/src/ui/treeviews/dataProviders/kubernetesObjectDataProvider.ts deleted file mode 100644 index 6de01160..00000000 --- a/src/ui/treeviews/dataProviders/kubernetesObjectDataProvider.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { getNamespace } from 'cli/kubernetes/kubectlGetNamespace'; -import { currentContextData } from 'data/contextData'; -import { KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { groupNodesByNamespace, sortNodes } from 'utils/treeNodeUtils'; -import { makeTreeNode } from '../nodes/makeTreeNode'; -import { NamespaceNode } from '../nodes/namespaceNode'; -import { TreeNode } from '../nodes/treeNode'; -import { AsyncDataProvider } from './asyncDataProvider'; - -/** - * Superclass for data providers that group objects by namespace: Source and Workload data providers - */ -export abstract class KubernetesObjectDataProvider extends AsyncDataProvider { - public namespaceNodeTreeItems(): NamespaceNode[] { - return (this.nodes?.filter(node => node instanceof NamespaceNode) as NamespaceNode[] || []); - } - - private findNamespaceNode(nsName?: string): NamespaceNode | undefined { - if(!nsName) { - return; - } - return this.namespaceNodeTreeItems().find(node => node.resource.metadata.name === nsName); - } - - private findParentNamespaceNode(object: KubernetesObject): NamespaceNode | undefined { - const nsName = object.metadata.namespace; - return this.findNamespaceNode(nsName); - } - - public async add(object: KubernetesObject) { - if(!object.metadata.namespace) { - return; - } - - let namespaceNode = this.findParentNamespaceNode(object); - if(!namespaceNode) { - const ns = await getNamespace(object.metadata.namespace); - if(!ns) { - return; - } - namespaceNode = new NamespaceNode(ns, this); - this.nodes?.push(namespaceNode); - sortNodes(this.nodes); - setTimeout(() => { - namespaceNode!.updateLabel(); - namespaceNode!.expand(); - this.redraw(namespaceNode); - }, 0); - } - - - if(namespaceNode.findChildByResource(object)) { - this.update(object); - namespaceNode.updateLabel(); - this.redraw(); - return; - } - - const resourceNode = makeTreeNode(object, this); - if(!resourceNode) { - return; - } - - namespaceNode.addChild(resourceNode); - sortNodes(namespaceNode.children); - - namespaceNode.updateLabel(); - this.redraw(); - } - - public update(object: KubernetesObject) { - const namespaceNode = this.findParentNamespaceNode(object); - if(!namespaceNode) { - return; - } - - const node = namespaceNode.findChildByResource(object); - if(node && node.resource) { - node.resource = object; - node.updateStatus(); - - setTimeout(() => { - namespaceNode!.updateLabel(); - this.redraw(namespaceNode); - }, 0); - - } - } - - public delete(object: KubernetesObject) { - const namespaceNode = this.findParentNamespaceNode(object); - if(!namespaceNode) { - return; - } - - const childNode = namespaceNode.findChildByResource(object); - if(childNode) { - namespaceNode.removeChild(childNode); - if(namespaceNode.children.length > 0) { - // namespace has other children - namespaceNode.updateLabel(); - this.redraw(namespaceNode); - } else { - // namespace has no more children. should be removed - this.nodes?.splice(this.nodes?.indexOf(namespaceNode), 1); - this.redraw(undefined); - } - } - } - - async expandAll() { - const resourceNodes: TreeNode[] = []; - - this.nodes.forEach(node => { - if (node instanceof NamespaceNode) { - const children = node.children as TreeNode[]; - resourceNodes.push(...children); - } - }); - - // rebuild top level nodes or the tree will not redraw - const viewData = this.viewData(currentContextData()); - [viewData.nodes] = await groupNodesByNamespace(resourceNodes, true, true); - this.redraw(); - } - - - // async updateNodeChildren(node: TreeNode) { - // if(node instanceof Canary) { - // await node.updateChildren(); - // } - // } - -} diff --git a/src/ui/treeviews/dataProviders/simpleDataProvider.ts b/src/ui/treeviews/dataProviders/simpleDataProvider.ts deleted file mode 100644 index b5916a66..00000000 --- a/src/ui/treeviews/dataProviders/simpleDataProvider.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { KubeConfigState, kubeConfigState } from 'cli/kubernetes/kubernetesConfig'; -import { InfoLabel, infoNodes } from 'utils/makeTreeviewInfoNode'; -import { Event, EventEmitter, TreeDataProvider, TreeItem } from 'vscode'; -import { TreeNode } from '../nodes/treeNode'; - - -/**` - * Defines tree view data provider base class for all GitOps tree views. - */ -export class SimpleDataProvider implements TreeDataProvider { - private _nodes: TreeNode[] = []; - - guid = ''; - - constructor() { - this.guid = Math.random().toString(36); - } - - get nodes() { - return this._nodes; - } - - protected loading = false; - - protected _onDidChangeTreeData: EventEmitter = new EventEmitter(); - readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - - - /* if treeItem is undefined, redraw all tree items */ - public redraw(treeItem?: TreeItem) { - this._onDidChangeTreeData.fire(treeItem); - } - - /** - * Gets tree view item for the specified tree element. - * @param element Tree element. - * @returns Tree view item. - */ - public getTreeItem(element: TreeItem): TreeItem { - return element; - } - - /** - * Gets tree element parent. - * @param element Tree item to get parent for. - * @returns Parent tree item or null for the top level nodes. - */ - public getParent(element: TreeItem): TreeItem | null { - if (element instanceof TreeNode && element.parent) { - return element.parent; - } - return null; - } - - // this is called by vscode treeview redraw to get the nodes to display - public async getChildren(element?: TreeItem): Promise { - if(!element) { - return this.getRootNodes(); - } else if (element instanceof TreeNode) { - return element.children; - } - - return []; - } - - // give nodes for vscode to render based on async data loading state - protected async getRootNodes(): Promise { - if (this.loading || kubeConfigState === KubeConfigState.Loading) { - return infoNodes(InfoLabel.Loading, this); - } - if(this.nodes.length === 0) { - return infoNodes(InfoLabel.NoResources, this); - } - - return this.nodes; - } - - - public async reload() { - if(this.loading) { - return; - } - - this.loading = true; - this._nodes = []; - this._nodes = await this.loadRootNodes(); - this.loading = false; - - this.redraw(); - } - - async loadRootNodes(): Promise { - return []; - } - -} - - diff --git a/src/ui/treeviews/dataProviders/sourceDataProvider.ts b/src/ui/treeviews/dataProviders/sourceDataProvider.ts deleted file mode 100644 index 88cbd4cc..00000000 --- a/src/ui/treeviews/dataProviders/sourceDataProvider.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { getBuckets, getGitRepositories, getHelmRepositories, getOciRepositories } from 'cli/kubernetes/kubectlGet'; -import { getNamespaces } from 'cli/kubernetes/kubectlGetNamespace'; -import { ContextData } from 'data/contextData'; -import { statusBar } from 'ui/statusBar'; -import { sortByMetadataName } from 'utils/sortByMetadataName'; -import { groupNodesByNamespace } from 'utils/treeNodeUtils'; -import { makeTreeNode } from '../nodes/makeTreeNode'; -import { BucketNode } from '../nodes/source/bucketNode'; -import { GitRepositoryNode } from '../nodes/source/gitRepositoryNode'; -import { HelmRepositoryNode } from '../nodes/source/helmRepositoryNode'; -import { OCIRepositoryNode } from '../nodes/source/ociRepositoryNode'; -import { SourceNode } from '../nodes/source/sourceNode'; -import { KubernetesObjectDataProvider } from './kubernetesObjectDataProvider'; - -/** - * Defines Sources data provider for loading Git/Helm repositories - * and Buckets in GitOps Sources tree view. - */ -export class SourceDataProvider extends KubernetesObjectDataProvider { - - protected viewData(contextData: ContextData) { - return contextData.viewData.source; - } - - /** - * Creates Source tree view items for the currently selected kubernetes cluster. - */ - async loadRootNodes() { - statusBar.startLoadingTree(); - - const nodes: SourceNode[] = []; - - // Fetch all sources asynchronously and at once - const [gitRepositories, ociRepositories, helmRepositories, buckets, _] = await Promise.all([ - getGitRepositories(), - getOciRepositories(), - getHelmRepositories(), - getBuckets(), - getNamespaces(), // cache namespaces - ]); - - // add git repositories to the tree - for (const gitRepository of sortByMetadataName(gitRepositories)) { - nodes.push(makeTreeNode(gitRepository, this) as GitRepositoryNode); - } - - // add oci repositories to the tree - for (const ociRepository of sortByMetadataName(ociRepositories)) { - nodes.push(makeTreeNode(ociRepository, this) as OCIRepositoryNode); - } - - for (const helmRepository of sortByMetadataName(helmRepositories)) { - nodes.push(makeTreeNode(helmRepository, this) as HelmRepositoryNode); - } - - // add buckets to the tree - for (const bucket of sortByMetadataName(buckets)) { - nodes.push(makeTreeNode(bucket, this) as BucketNode); - } - - statusBar.stopLoadingTree(); - - const [groupedNodes] = await groupNodesByNamespace(nodes, false, true); - return groupedNodes; - } -} diff --git a/src/ui/treeviews/dataProviders/wgeDataProvider.ts b/src/ui/treeviews/dataProviders/wgeDataProvider.ts deleted file mode 100644 index cb119d48..00000000 --- a/src/ui/treeviews/dataProviders/wgeDataProvider.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { getCanaries, getGitOpsSet, getGitOpsTemplates, getPipelines } from 'cli/kubernetes/kubectlGet'; -import { ContextData } from 'data/contextData'; -import { sortByMetadataName } from 'utils/sortByMetadataName'; -import { groupNodesByNamespace } from 'utils/treeNodeUtils'; -import { CanaryNode } from '../nodes/wge/canaryNode'; -import { GitOpsSetNode } from '../nodes/wge/gitOpsSetNode'; -import { GitOpsTemplateNode } from '../nodes/wge/gitOpsTemplateNode'; -import { PipelineNode } from '../nodes/wge/pipelineNode'; -import { CanariesContainerNode, GitOpsSetsContainerNode, PipelinesContainerNode, TemplatesContainerNode } from '../nodes/wge/wgeNodes'; -import { AsyncDataProvider } from './asyncDataProvider'; - -export class WgeDataProvider extends AsyncDataProvider { - protected viewData(contextData: ContextData) { - return contextData.viewData.wge; - } - - async loadRootNodes() { - const nodes = []; - - const [templates, canaries, pipelines, gitopssets] = await Promise.all([ - getGitOpsTemplates(), - getCanaries(), - getPipelines(), - getGitOpsSet(), - ]); - - - // TEMPLATES - const ts = new TemplatesContainerNode(); - nodes.push(ts); - - for (const t of sortByMetadataName(templates)) { - const node = new GitOpsTemplateNode(t); - ts.addChild(node); - } - - // CANARIES - const cs = new CanariesContainerNode(); - nodes.push(cs); - - for (const c of sortByMetadataName(canaries)) { - const node = new CanaryNode(c); - cs.addChild(node); - node.updateChildren(); - } - [cs.children] = await groupNodesByNamespace(cs.children, false, true); - - // PIPELINES - const ps = new PipelinesContainerNode(); - nodes.push(ps); - - for (const p of sortByMetadataName(pipelines)) { - const node = new PipelineNode(p); - ps.addChild(node); - node.updateChildren(); - } - [ps.children] = await groupNodesByNamespace(ps.children, false, true); - - - // GITOPSSETS - const gops = new GitOpsSetsContainerNode(); - nodes.push(gops); - - for (const g of sortByMetadataName(gitopssets)) { - const node = new GitOpsSetNode(g); - gops.addChild(node); - } - // log(gops.children); - [gops.children] = await groupNodesByNamespace(gops.children, false, true); - // log(gops.children); - - return nodes; - } - - -} - - diff --git a/src/ui/treeviews/dataProviders/workloadDataProvider.ts b/src/ui/treeviews/dataProviders/workloadDataProvider.ts deleted file mode 100644 index 7419673b..00000000 --- a/src/ui/treeviews/dataProviders/workloadDataProvider.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { getHelmReleases, getKustomizations } from 'cli/kubernetes/kubectlGet'; -import { getNamespaces } from 'cli/kubernetes/kubectlGetNamespace'; -import { ContextData } from 'data/contextData'; -import { statusBar } from 'ui/statusBar'; -import { sortByMetadataName } from 'utils/sortByMetadataName'; -import { groupNodesByNamespace } from 'utils/treeNodeUtils'; -import { makeTreeNode } from '../nodes/makeTreeNode'; -import { HelmReleaseNode } from '../nodes/workload/helmReleaseNode'; -import { KustomizationNode } from '../nodes/workload/kustomizationNode'; -import { WorkloadNode } from '../nodes/workload/workloadNode'; -import { KubernetesObjectDataProvider } from './kubernetesObjectDataProvider'; - -/**- - * Defines data provider for loading Kustomizations - * and Helm Releases in Workloads Tree View. - */ -export class WorkloadDataProvider extends KubernetesObjectDataProvider { - protected viewData(contextData: ContextData) { - return contextData.viewData.workload; - } - - /** - * Creates Workload tree nodes for the currently selected kubernetes cluster. - */ - async loadRootNodes() { - statusBar.startLoadingTree(); - - const nodes: WorkloadNode[] = []; - - const [kustomizations, helmReleases, _] = await Promise.all([ - // Fetch all workloads - getKustomizations(), - getHelmReleases(), - // Cache namespaces to group the nodes - getNamespaces(), - ]); - - for (const k of sortByMetadataName(kustomizations)) { - nodes.push(makeTreeNode(k, this) as KustomizationNode); - } - - for (const hr of sortByMetadataName(helmReleases)) { - nodes.push(makeTreeNode(hr, this) as HelmReleaseNode); - } - - for (const node of nodes) { - node.updateChildren(); - } - - statusBar.stopLoadingTree(); - - const [groupedNodes] = await groupNodesByNamespace(nodes, false, true); - return groupedNodes; - } - -} - diff --git a/src/ui/treeviews/nodes/anyResourceNode.ts b/src/ui/treeviews/nodes/anyResourceNode.ts deleted file mode 100644 index 31c6599d..00000000 --- a/src/ui/treeviews/nodes/anyResourceNode.ts +++ /dev/null @@ -1,23 +0,0 @@ - -import { KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { SimpleDataProvider } from '../dataProviders/simpleDataProvider'; -import { KubernetesObjectNode } from './kubernetesObjectNode'; - -/** - * Defines any kubernetes resourse. - */ -export class AnyResourceNode extends KubernetesObjectNode { - constructor(anyResource: KubernetesObject, dataProvider: SimpleDataProvider) { - super(anyResource, anyResource.metadata.name || '', dataProvider); - - this.description = anyResource.kind; - } - - get tooltip() { - if(this.resource.metadata.namespace) { - return `Namespace: ${this.resource.metadata.namespace}`; - } else { - return ''; - } - } -} diff --git a/src/ui/treeviews/nodes/cluster/clusterNode.ts b/src/ui/treeviews/nodes/cluster/clusterNode.ts deleted file mode 100644 index 0e990adc..00000000 --- a/src/ui/treeviews/nodes/cluster/clusterNode.ts +++ /dev/null @@ -1,234 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import { ExtensionMode, MarkdownString } from 'vscode'; - - -import { fluxVersion } from 'cli/checkVersions'; -import { fluxTools } from 'cli/flux/fluxTools'; -import { ApiState } from 'cli/kubernetes/apiResources'; -import { detectClusterProvider } from 'cli/kubernetes/clusterProvider'; -import { getFluxControllers } from 'cli/kubernetes/kubectlGet'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { currentContextData } from 'data/contextData'; -import { extensionContext, globalState, setVSCodeContext } from 'extension'; -import { CommandId, ContextId } from 'types/extensionIds'; -import { ClusterProvider } from 'types/kubernetes/clusterProvider'; -import { NodeContext } from 'types/nodeContext'; -import { clusterDataProvider, revealClusterNode } from 'ui/treeviews/treeViews'; -import { InfoLabel } from 'utils/makeTreeviewInfoNode'; -import { createContextMarkdownTable, createMarkdownHr } from 'utils/markdownUtils'; -import { ClusterDeploymentNode } from './clusterDeploymentNode'; -import { ClusterTreeNode } from './clusterTreeNode'; - -/** - * Defines Cluster context tree view item for displaying - * kubernetes contexts inside the Clusters tree view. - */ -export class ClusterNode extends ClusterTreeNode { - - /** - * Whether cluster is managed by AKS or Azure ARC - * or some other provider. - */ - private clusterProvider: ClusterProvider = ClusterProvider.Unknown; - - /** - * User used "Set Cluster Provider" context menu item - * to override the cluster provider detection. - */ - private clusterProviderManuallyOverridden = false; - - /** - * Cluster object. - */ - public cluster?: k8s.Cluster; - - /** - * Cluster context. - */ - public context: k8s.Context; - - /** - * Whether or not gitops is installed on this cluster. - * `undefined` when it's not yet initialized or when detection failed. - */ - isGitOpsEnabled?: boolean; - - /** - * Creates new Cluster tree view item for display. - * @param kubernetesContext Cluster object info. - */ - constructor(context: k8s.Context) { - super(context.name); - - this.cluster = kubeConfig.getCluster(context.cluster) || undefined; - this.context = context; - this.description = this.cluster?.server; - - this.setIcon('cloud'); - } - - /** - * Set context/icon and refresh the node: - * - Whether or not GitOps is enabled - * - Cluster provider. - */ - async updateNodeChildren() { - this.updateControllersNodes(); - - // set cluster provider - const clusterMetadata = globalState.getClusterMetadata(this.cluster?.name || this.context.name); - if (clusterMetadata?.clusterProvider) { - this.clusterProviderManuallyOverridden = true; - } - this.clusterProvider = clusterMetadata?.clusterProvider || await detectClusterProvider(this.context.name); - - // Update vscode context for welcome view of other tree views - if (this.isCurrent && typeof this.isGitOpsEnabled === 'boolean') { - setVSCodeContext(ContextId.CurrentClusterGitOpsNotEnabled, !this.isGitOpsEnabled); - } - - // icon - if (this.isGitOpsEnabled) { - this.setIcon('cloud-gitops'); - } else { - this.setIcon('cloud'); - } - - clusterDataProvider.redraw(); - this.updateControllersStatus(); - } - - private async updateControllersNodes() { - const contextData = currentContextData(); - if(contextData.contextName !== this.context.name) { - return; - } - - if(contextData.apiState === ApiState.ClusterUnreachable) { - this.children = this.infoNodes(InfoLabel.ClusterUnreachable); - return; - } - if(contextData.apiState === ApiState.Loading) { - this.children = this.infoNodes(InfoLabel.LoadingApi); - return; - } - - const fluxControllers = await getFluxControllers(this.context.name); - this.isGitOpsEnabled = fluxControllers.length !== 0; - - this.children = []; - if (this.isGitOpsEnabled) { - revealClusterNode(this, { - expand: false, - }); - for (const deployment of fluxControllers) { - this.addChild(new ClusterDeploymentNode(deployment)); - } - } else { - const notFound = new ClusterTreeNode('Flux controllers not found'); - notFound.setIcon('warning'); - this.addChild(notFound); - } - } - - /** - * Update deployment status for flux controllers. - * Get status from running flux commands instead of kubectl. - */ - private async updateControllersStatus() { - const contextData = currentContextData(); - if(contextData.contextName !== this.context.name) { - return; - } - - if (this.children.length === 0 || contextData.apiState === ApiState.ClusterUnreachable) { - return; - } - const fluxCheckResult = await fluxTools.check(this.context.name); - if (!fluxCheckResult) { - return; - } - - const deploymentNodes: ClusterDeploymentNode[] = this.children.filter(node => node instanceof ClusterDeploymentNode) as ClusterDeploymentNode[]; - // Match controllers fetched with flux with controllers - // fetched with kubectl and update tree nodes. - for (const clusterController of deploymentNodes) { - for (const controller of fluxCheckResult.controllers) { - const clusterControllerName = clusterController.resource.metadata.name?.trim(); - const deploymentName = controller.name.trim(); - - if (clusterControllerName === deploymentName) { - clusterController.description = controller.status; - if (controller.success) { - clusterController.setStatus('success'); - } else { - clusterController.setStatus('failure'); - } - } - } - clusterDataProvider.redraw(this); - } - } - - - get isCurrent(): boolean { - return this.context.name === kubeConfig.getCurrentContext(); - } - - get tooltip(): MarkdownString { - return this.getMarkdownHover(); - } - - /** - * Creates markdwon string for the Cluster tree view item tooltip. - */ - getMarkdownHover(): MarkdownString { - const markdown: MarkdownString = createContextMarkdownTable(this.context, this.cluster); - - createMarkdownHr(markdown); - if(this.context.name === kubeConfig.getCurrentContext()) { - markdown.appendMarkdown(`Flux Version: ${fluxVersion}`); - } - - if (this.clusterProvider !== ClusterProvider.Generic || this.clusterProviderManuallyOverridden) { - createMarkdownHr(markdown); - markdown.appendMarkdown(`Cluster Provider: ${this.clusterProvider}`); - if (this.clusterProviderManuallyOverridden) { - markdown.appendMarkdown(' (User override)'); - } - } - - return markdown; - } - - // @ts-ignore - get command() { - // Allow click to swith current kubernetes context only when developing extension - if (extensionContext.extensionMode === ExtensionMode.Development) { - return { - command: CommandId.SetCurrentKubernetesContext, - arguments: [this], - title: 'Set Context', - }; - } - } - - get contexts() { - const cs = []; - - if (typeof this.isGitOpsEnabled === 'boolean') { - cs.push( - this.isGitOpsEnabled ? NodeContext.ClusterGitOpsEnabled : NodeContext.ClusterGitOpsNotEnabled, - ); - } - - cs.push( - this.isCurrent ? NodeContext.CurrentCluster : NodeContext.NotCurrentCluster, - ); - - cs.push(NodeContext.Cluster); - - return cs; - } - -} diff --git a/src/ui/treeviews/nodes/cluster/clusterTreeNode.ts b/src/ui/treeviews/nodes/cluster/clusterTreeNode.ts deleted file mode 100644 index 3e57e0bb..00000000 --- a/src/ui/treeviews/nodes/cluster/clusterTreeNode.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { clusterDataProvider } from 'ui/treeviews/treeViews'; -import { TreeNode } from '../treeNode'; - -export class ClusterTreeNode extends TreeNode { - constructor(label: string) { - super(label, clusterDataProvider); - } -} diff --git a/src/ui/treeviews/nodes/kubernetesObjectNode.ts b/src/ui/treeviews/nodes/kubernetesObjectNode.ts deleted file mode 100644 index 4a68b182..00000000 --- a/src/ui/treeviews/nodes/kubernetesObjectNode.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { CommandId } from 'types/extensionIds'; -import { FileTypes } from 'types/fileTypes'; -import { KubernetesObject, qualifyToolkitKind } from 'types/kubernetes/kubernetesTypes'; -import { getResourceUri } from 'utils/getResourceUri'; -import { KnownTreeNodeResources, createMarkdownTable } from 'utils/markdownUtils'; -import { SimpleDataProvider } from '../dataProviders/simpleDataProvider'; -import { TreeNode } from './treeNode'; - - - -export class KubernetesObjectNode extends TreeNode { - /** - * Kubernetes resource. - */ - resource: KubernetesObject; - - constructor(resource: KubernetesObject, label: string, dataProvider: SimpleDataProvider) { - super(label, dataProvider); - - this.resource = resource; - } - - fullyQualifyKind(): string { - return qualifyToolkitKind(this.resource.kind); - } - - // @ts-ignore - get tooltip(): string | MarkdownString { - if (this.resource) { - return createMarkdownTable(this.resource as KnownTreeNodeResources); - } - } - - // @ts-ignore - get command(): Command | undefined { - // Set click event handler to load kubernetes resource as yaml file in editor. - if (this.resource) { - let stringKind = this.fullyQualifyKind(); - const resourceUri = getResourceUri( - this.resource.metadata.namespace, - `${stringKind}/${this.resource.metadata.name}`, - FileTypes.Yaml, - ); - - return { - command: CommandId.EditorOpenResource, - arguments: [resourceUri], - title: 'View Resource', - }; - } - } - - findChildByResource(resource: KubernetesObject): KubernetesObjectNode | undefined { - const found = this.children.find(child => { - if (child instanceof KubernetesObjectNode) { - return child.resource.metadata.name === resource.metadata.name && - child.resource.kind === resource.kind && - child.resource.metadata.namespace === resource.metadata.namespace; - } - }); - if (found) { - return found as KubernetesObjectNode; - } - } - - get viewStateKey(): string { - const parentViewKey = this.parent?.viewStateKey; - return this.resource.metadata.uid + parentViewKey + this.dataProvider.guid; - } - - get contextType(): string | undefined { - return this.resource.kind; - } -} diff --git a/src/ui/treeviews/nodes/makeTreeNode.ts b/src/ui/treeviews/nodes/makeTreeNode.ts deleted file mode 100644 index ffbd6e1f..00000000 --- a/src/ui/treeviews/nodes/makeTreeNode.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { KubernetesObject } from '@kubernetes/client-node'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { SimpleDataProvider } from '../dataProviders/simpleDataProvider'; -import { AnyResourceNode } from './anyResourceNode'; -import { NamespaceNode } from './namespaceNode'; -import { BucketNode } from './source/bucketNode'; -import { GitRepositoryNode } from './source/gitRepositoryNode'; -import { HelmRepositoryNode } from './source/helmRepositoryNode'; -import { OCIRepositoryNode } from './source/ociRepositoryNode'; -import { TreeNode } from './treeNode'; -import { CanaryNode } from './wge/canaryNode'; -import { GitOpsSetNode } from './wge/gitOpsSetNode'; -import { GitOpsTemplateNode } from './wge/gitOpsTemplateNode'; -import { HelmReleaseNode } from './workload/helmReleaseNode'; -import { KustomizationNode } from './workload/kustomizationNode'; - -// eslint-disable-next-line @typescript-eslint/ban-types -const nodeConstructors = { - 'Bucket': BucketNode, - 'GitRepository': GitRepositoryNode, - 'OCIRepository': OCIRepositoryNode, - 'HelmRepository': HelmRepositoryNode, - 'HelmRelease': HelmReleaseNode, - 'Kustomization': KustomizationNode, - 'Canary': CanaryNode, - 'GitOpsTemplate': GitOpsTemplateNode, - 'GitOpsSet': GitOpsSetNode, - 'Pipeline': GitOpsSetNode, - - 'Namespace': NamespaceNode, - - 'Deployment': AnyResourceNode, - 'Node': AnyResourceNode, - 'Pod': AnyResourceNode, - 'ConfigMap': AnyResourceNode, - 'GitopsCluster': AnyResourceNode, -}; - -export function makeTreeNode(object: KubernetesObject, dataProvider: SimpleDataProvider): TreeNode { - let constructor = nodeConstructors[object.kind as Kind]; - if(!constructor) { - constructor = AnyResourceNode; - } - return new constructor(object as any, dataProvider); -} diff --git a/src/ui/treeviews/nodes/namespaceNode.ts b/src/ui/treeviews/nodes/namespaceNode.ts deleted file mode 100644 index 5189c3d9..00000000 --- a/src/ui/treeviews/nodes/namespaceNode.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Kind, Namespace } from 'types/kubernetes/kubernetesTypes'; -import { CommonIcon } from 'ui/icons'; -import { TreeItemCollapsibleState } from 'vscode'; -import { SimpleDataProvider } from '../dataProviders/simpleDataProvider'; -import { KubernetesObjectNode } from './kubernetesObjectNode'; -import { SourceNode } from './source/sourceNode'; -import { WgeNode } from './wge/wgeNodes'; -import { WorkloadNode } from './workload/workloadNode'; - -/** - * Defines any kubernetes resourse. - */ -export class NamespaceNode extends KubernetesObjectNode { - - /** - * kubernetes resource metadata - */ - resource: Namespace; - - constructor(namespace: Namespace, dataProvider: SimpleDataProvider) { - super(namespace, namespace.metadata.name, dataProvider); - - this.description = Kind.Namespace; - - this.resource = namespace; - } - - updateLabel(withIcons = true) { - const totalLength = this.children.length; - let readyLength = 0; - let loadingLength = 0; - for(const child of this.children) { - if(child instanceof SourceNode || child instanceof WorkloadNode || child instanceof WgeNode) { - if(child.resourceIsReady) { - readyLength++; - } else if(child.resourceIsProgressing) { - loadingLength++; - } - } else { - readyLength++; - } - } - - const validLength = readyLength + loadingLength; - if(withIcons) { - if(readyLength === totalLength) { - this.setCommonIcon(CommonIcon.Success); - } else if(validLength === totalLength) { - this.setCommonIcon(CommonIcon.Progressing); - } else { - this.setCommonIcon(CommonIcon.Warning); - } - } else { - this.setIcon(undefined); - } - - if(this.collapsibleState === TreeItemCollapsibleState.Collapsed) { - const lengthLabel = totalLength === validLength ? `${totalLength}` : `${validLength}/${totalLength}`; - this.label = `${this.resource.metadata.name} (${lengthLabel})`; - } else { - this.label = `${this.resource.metadata.name}`; - } - } -} diff --git a/src/ui/treeviews/nodes/source/bucketNode.ts b/src/ui/treeviews/nodes/source/bucketNode.ts deleted file mode 100644 index 8c2e7e1a..00000000 --- a/src/ui/treeviews/nodes/source/bucketNode.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Bucket } from 'types/flux/bucket'; -import { SourceNode } from './sourceNode'; - -/** - * Defines Bucket tree view item for display in GitOps Sources tree view. - */ -export class BucketNode extends SourceNode { - /** - * Bucket kubernetes resource object - */ - resource!: Bucket; -} diff --git a/src/ui/treeviews/nodes/source/gitRepositoryNode.ts b/src/ui/treeviews/nodes/source/gitRepositoryNode.ts deleted file mode 100644 index 0ae35e41..00000000 --- a/src/ui/treeviews/nodes/source/gitRepositoryNode.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { GitRepository } from 'types/flux/gitRepository'; -import { SourceNode } from './sourceNode'; - -/** - * Defines GitRepository tree view item for display in GitOps Sources tree view. - */ -export class GitRepositoryNode extends SourceNode { - resource!: GitRepository; -} diff --git a/src/ui/treeviews/nodes/source/helmRepositoryNode.ts b/src/ui/treeviews/nodes/source/helmRepositoryNode.ts deleted file mode 100644 index 4b35e8eb..00000000 --- a/src/ui/treeviews/nodes/source/helmRepositoryNode.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { HelmRepository } from 'types/flux/helmRepository'; -import { SourceNode } from './sourceNode'; - -/** - * Defines HelmRepository tree view item for display in GitOps Sources tree view. - */ -export class HelmRepositoryNode extends SourceNode { - resource!: HelmRepository; -} diff --git a/src/ui/treeviews/nodes/source/ociRepositoryNode.ts b/src/ui/treeviews/nodes/source/ociRepositoryNode.ts deleted file mode 100644 index bd98df64..00000000 --- a/src/ui/treeviews/nodes/source/ociRepositoryNode.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { OCIRepository } from 'types/flux/ociRepository'; -import { SourceNode } from './sourceNode'; - -/** - * Defines OCIRepository tree view item for display in GitOps Sources tree view. - */ -export class OCIRepositoryNode extends SourceNode { - resource!: OCIRepository; - - /** - * Creates new oci repository tree view item for display. - * @param ociRepository OCI repository kubernetes object info. - */ - constructor(ociRepository: OCIRepository) { - super(ociRepository); - } - -} diff --git a/src/ui/treeviews/nodes/source/sourceNode.ts b/src/ui/treeviews/nodes/source/sourceNode.ts deleted file mode 100644 index 67bbfa9a..00000000 --- a/src/ui/treeviews/nodes/source/sourceNode.ts +++ /dev/null @@ -1,29 +0,0 @@ - -import { FluxSourceObject } from 'types/flux/object'; -import { NodeContext } from 'types/nodeContext'; -import { SimpleDataProvider } from 'ui/treeviews/dataProviders/simpleDataProvider'; -import { sourceDataProvider } from 'ui/treeviews/treeViews'; -import { shortenRevision } from 'utils/stringUtils'; -import { ToolkitNode } from '../toolkitNode'; -/** - * Base class for all the Source tree view items. - */ -export class SourceNode extends ToolkitNode { - resource!: FluxSourceObject; - dataProvider!: SimpleDataProvider; - - - constructor(resource: FluxSourceObject) { - super(resource, sourceDataProvider); - } - - get revision() { - return shortenRevision(this.resource.status.artifact?.revision); - } - - get contexts() { - return this.resource.spec.suspend ? [NodeContext.Suspend] : [NodeContext.NotSuspend]; - } - - -} diff --git a/src/ui/treeviews/nodes/toolkitNode.ts b/src/ui/treeviews/nodes/toolkitNode.ts deleted file mode 100644 index 0c612c3f..00000000 --- a/src/ui/treeviews/nodes/toolkitNode.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { ToolkitObject } from 'types/flux/object'; -import { Condition, Kind } from 'types/kubernetes/kubernetesTypes'; -import { CommonIcon } from 'ui/icons'; -import { createMarkdownError, createMarkdownHr, createMarkdownTable } from 'utils/markdownUtils'; -import { SimpleDataProvider } from '../dataProviders/simpleDataProvider'; -import { KubernetesObjectNode } from './kubernetesObjectNode'; - -export enum ReconcileState { - Ready, - Failed, - Progressing, -} - -export class ToolkitNode extends KubernetesObjectNode { - resource!: ToolkitObject; - reconcileState: ReconcileState = ReconcileState.Progressing; - - constructor(resource: ToolkitObject, dataProvider: SimpleDataProvider) { - super(resource, `${resource.kind}: ${resource.metadata.name}`, dataProvider); - - this.updateStatus(); - } - - /** - * Update status with showing error icon when fetch failed. - * @param source target source - */ - updateStatus() { - const condition = this.readyOrFirstCondition; - if (condition?.status === 'True') { - this.reconcileState = ReconcileState.Ready; - this.setCommonIcon(CommonIcon.Success); - } else if (condition?.reason === 'Progressing' || condition?.reason === 'Promoting' || condition?.reason === 'Finalising') { - this.reconcileState = ReconcileState.Progressing; - this.setCommonIcon(CommonIcon.Progressing); - } else { - this.reconcileState = ReconcileState.Failed; - this.setCommonIcon(CommonIcon.Error); - } - } - - /** - * Find condition with the "Ready" type or - * return first one if "Ready" not found. - */ - get readyOrFirstCondition(): Condition | undefined { - const conditions = this.resource.status.conditions; - - if (Array.isArray(conditions)) { - return conditions.find(condition => condition.type === 'Ready') || conditions[0]; - } else { - return conditions; - } - } - - get tooltip() { - const markdown = createMarkdownTable(this.resource); - // show status in hoverwhat failed - if (!this.resourceIsReady) { - createMarkdownHr(markdown); - createMarkdownError('Status message', this.readyOrFirstCondition?.message, markdown); - createMarkdownError('Status reason', this.readyOrFirstCondition?.reason, markdown); - } - - return markdown; - } - - get resourceIsReady(): boolean { - return this.reconcileState === ReconcileState.Ready; - } - - get resourceIsProgressing(): boolean { - return this.reconcileState === ReconcileState.Progressing; - } - - // @ts-ignore - get description() { - let revisionOrError = ''; - - if (!this.resourceIsReady) { - revisionOrError = `${this.readyOrFirstCondition?.reason || ''}`; - if(this.resource.kind === Kind.Canary) { - revisionOrError = `${revisionOrError} ${this.resource.status?.canaryWeight}%`; - } - } else { - revisionOrError = this.revision; - } - - return `${this.isSuspendIcon}${revisionOrError}`; - } - - get revision(): string { - return 'unknown'; - } - - get isSuspendIcon(): string { - if(this.resource.kind !== Kind.Pipeline) { - return this.resource.spec?.suspend ? '⏸ ' : ''; - } - return ''; - } - -} diff --git a/src/ui/treeviews/nodes/wge/canaryNode.ts b/src/ui/treeviews/nodes/wge/canaryNode.ts deleted file mode 100644 index c58dbd75..00000000 --- a/src/ui/treeviews/nodes/wge/canaryNode.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { getCanaryChildren } from 'cli/kubernetes/kubectlGet'; -import { currentContextData } from 'data/contextData'; -import { Canary } from 'types/flux/canary'; -import { NodeContext } from 'types/nodeContext'; -import { InfoLabel } from 'utils/makeTreeviewInfoNode'; -import { groupNodesByNamespace } from 'utils/treeNodeUtils'; -import { AnyResourceNode } from '../anyResourceNode'; -import { WgeNode } from './wgeNodes'; - -export class CanaryNode extends WgeNode { - resource!: Canary; - - get revision() { - // return shortenRevision(this.resource.status.lastAppliedRevision); - return `${this.resource.status.phase} ${this.resource.status.lastAppliedSpec || ''}`; - } - - get contexts() { - return [NodeContext.HasWgePortal]; - } - - get wgePortalQuery() { - const name = this.resource.metadata.name; - const namespace = this.resource.metadata.namespace || 'default'; - const clusterName = currentContextData().wgeClusterName; - - return `canary_details/details?clusterName=${clusterName}&name=${name}&namespace=${namespace}`; - } - - - - async updateChildren() { - // deployment/-primary - if(!this.resource.metadata.name) { - return; - } - - this.children = this.infoNodes(InfoLabel.Loading); - this.redraw(); - - const [children, primary] = await Promise.all([getCanaryChildren(this.resource.metadata.name), getCanaryChildren(`${this.resource.metadata.name}-primary`)]); - const canaryChildren = [...children, ...primary]; - - if (!canaryChildren) { - this.children = this.infoNodes(InfoLabel.FailedToLoad); - this.redraw(); - return; - } - - if (canaryChildren.length === 0) { - this.children = this.infoNodes(InfoLabel.NoResources); - this.redraw(); - return; - } - - const childrenNodes = canaryChildren.map(child => new AnyResourceNode(child, this.dataProvider)); - const [groupedNodes, clusterScopedNodes] = await groupNodesByNamespace(childrenNodes); - this.children = [...groupedNodes, ...clusterScopedNodes]; - - this.redraw(); - return; - } - -} diff --git a/src/ui/treeviews/nodes/wge/environmentNode.ts b/src/ui/treeviews/nodes/wge/environmentNode.ts deleted file mode 100644 index d99d896d..00000000 --- a/src/ui/treeviews/nodes/wge/environmentNode.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { getResource } from 'cli/kubernetes/kubectlGet'; -import { GitOpsCluster } from 'types/flux/gitOpsCluster'; -import { LocalAppReference, Pipeline, PipelineEnvironment, PipelineTarget } from 'types/flux/pipeline'; -import { Kind, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { themeIcon } from 'ui/icons'; -import { wgeDataProvider } from 'ui/treeviews/treeViews'; -import { makeTreeNode } from '../makeTreeNode'; -import { TreeNode } from '../treeNode'; - -export class PipelineEnvironmentNode extends TreeNode { - environment: PipelineEnvironment; - pipepine: Pipeline; - - constructor(environment: PipelineEnvironment, pipeline: Pipeline) { - super(environment.name, wgeDataProvider); - - this.makeCollapsible(); - this.environment = environment; - this.pipepine = pipeline; - - this.description = 'Environment'; - } - - async updateChildren() { - const targets = this.environment.targets; - for(const target of targets) { - const targetCluster = await this.getTargetCluster(target); - const appRef = this.pipepine.spec.appRef; - const targetNode = new PipelineTargetNode(target, appRef, targetCluster); - targetNode.updateChildren(); - this.addChild(targetNode); - } - this.redraw(); - } - - async getTargetCluster(target: PipelineTarget): Promise { - if(target.clusterRef) { - const namespace = target.clusterRef.namespace || this.pipepine.metadata.namespace || 'default'; - const cluster = await getResource(target.clusterRef.name, namespace, target.clusterRef.kind as Kind); - return cluster; - } - // if no clusterRef is set then the current cluster is the target cluster - return undefined; - } -} - - -export class PipelineTargetNode extends TreeNode { - target: PipelineTarget; - targetCluster?: GitOpsCluster; - appRef: LocalAppReference; - - constructor(target: PipelineTarget, appRef: LocalAppReference, targetCluster?: GitOpsCluster) { - const clusterLabel = targetCluster ? `${targetCluster.metadata.name}.${targetCluster.metadata.namespace}` : '(this cluster)'; - super(`${clusterLabel} ${appRef.name}.${target.namespace}`, wgeDataProvider); - - this.target = target; - this.targetCluster = targetCluster; - this.appRef = appRef; - - this.makeCollapsible(); - - this.description = 'Target'; - } - - async updateChildren() { - if(this.targetCluster) { - const gopsClusterNode = makeTreeNode(this.targetCluster, wgeDataProvider); - this.addChild(gopsClusterNode); - - const crossClusterAppNode = new TreeNode(`${this.appRef.name}.${this.target.namespace}`, wgeDataProvider); - crossClusterAppNode.description = `${this.appRef.kind} (in ${this.targetCluster.metadata.name})`; - crossClusterAppNode.setIcon(themeIcon('link-external', 'descriptionForeground')); - this.addChild(crossClusterAppNode); - } else { - const localHr = await getResource(this.appRef.name, this.target.namespace, this.appRef.kind as Kind); - if(localHr) { - const localAppNode = makeTreeNode(localHr, wgeDataProvider); - localAppNode.label = `${this.appRef.kind}: ${this.appRef.name}.${this.target.namespace}`; - localAppNode.updateChildren(); - this.addChild(localAppNode); - } - } - - this.redraw(); - } - - - - - - -} - diff --git a/src/ui/treeviews/nodes/wge/gitOpsSetNode.ts b/src/ui/treeviews/nodes/wge/gitOpsSetNode.ts deleted file mode 100644 index 40441796..00000000 --- a/src/ui/treeviews/nodes/wge/gitOpsSetNode.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { currentContextData } from 'data/contextData'; -import { GitOpsSet } from 'types/flux/gitopsset'; -import { NodeContext } from 'types/nodeContext'; -import { WgeNode } from './wgeNodes'; - -export class GitOpsSetNode extends WgeNode { - resource!: GitOpsSet; - - constructor(gitOpsSet: GitOpsSet) { - super(gitOpsSet); - - this.makeUncollapsible(); - } - - get revision() { - const condition = this.readyOrFirstCondition; - return condition?.lastTransitionTime ? `${condition?.lastTransitionTime.toLocaleString()}` : ''; - } - - get contexts() { - const cs = this.resource.spec.suspend ? [NodeContext.Suspend] : [NodeContext.NotSuspend]; - cs.push(NodeContext.HasWgePortal); - return cs; - } - - - get wgePortalQuery() { - const name = this.resource.metadata.name; - const namespace = this.resource.metadata.namespace || 'default'; - const clusterName = currentContextData().wgeClusterName; - - return `gitopssets/object/details?clusterName=${clusterName}&name=${name}&namespace=${namespace}`; - } - - -} diff --git a/src/ui/treeviews/nodes/wge/gitOpsTemplateNode.ts b/src/ui/treeviews/nodes/wge/gitOpsTemplateNode.ts deleted file mode 100644 index 6ee82cc0..00000000 --- a/src/ui/treeviews/nodes/wge/gitOpsTemplateNode.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { MarkdownString } from 'vscode'; - -import { GitOpsTemplate } from 'types/flux/gitOpsTemplate'; -import { NodeContext } from 'types/nodeContext'; -import { themeIcon } from 'ui/icons'; -import { wgeDataProvider } from 'ui/treeviews/treeViews'; -import { createMarkdownTable } from 'utils/markdownUtils'; -import { KubernetesObjectNode } from '../kubernetesObjectNode'; - -export enum TemplateType { - Cluster = 'cluster', - Application = 'application', - Pipeline = 'pipeline', -} - -export class GitOpsTemplateNode extends KubernetesObjectNode { - resource: GitOpsTemplate; - - constructor(template: GitOpsTemplate) { - super(template, template.metadata.name, wgeDataProvider); - - this.resource = template; - - if(this.templateType === 'cluster') { - this.setIcon(themeIcon('server-environment', 'descriptionForeground')); - } else if (this.templateType === 'application') { - this.setIcon(themeIcon('preview', 'descriptionForeground')); - } else if (this.templateType === 'pipeline') { - this.setIcon(themeIcon('rocket', 'descriptionForeground')); - - } - this.makeUncollapsible(); - } - - get tooltip() { - return this.getMarkdownHover(this.resource); - } - - get templateType(): TemplateType { - switch(this.resource.metadata.labels?.['weave.works/template-type']){ - case 'cluster': - return TemplateType.Cluster; - case 'application': - return TemplateType.Application; - case 'pipeline': - return TemplateType.Pipeline; - default: - return TemplateType.Application; - } - } - - // @ts-ignore - get description() { - return false; - } - - getMarkdownHover(template: GitOpsTemplate): MarkdownString { - const markdown: MarkdownString = createMarkdownTable(template); - return markdown; - } - - get contexts() { - return [NodeContext.HasWgePortal]; - } - - - get wgePortalQuery() { - const name = this.resource.metadata.name; - const namespace = this.resource.metadata.namespace || 'default'; - - - return `templates/create?name=${name}&namespace=${namespace}`; - } - - -} diff --git a/src/ui/treeviews/nodes/wge/pipelineNode.ts b/src/ui/treeviews/nodes/wge/pipelineNode.ts deleted file mode 100644 index a1d43e36..00000000 --- a/src/ui/treeviews/nodes/wge/pipelineNode.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Pipeline } from 'types/flux/pipeline'; -import { NodeContext } from 'types/nodeContext'; -import { TreeNode } from '../treeNode'; -import { PipelineEnvironmentNode } from './environmentNode'; -import { WgeNode } from './wgeNodes'; - -export class PipelineNode extends WgeNode { - resource!: Pipeline; - - constructor(pipeline: Pipeline) { - super(pipeline); - } - - get revision() { - const condition = this.readyOrFirstCondition; - return condition?.lastTransitionTime ? `${condition?.lastTransitionTime.toLocaleString()}` : ''; - } - - get contexts() { - const promotionContext = this.isManualPromotion ? NodeContext.ManualPromotion : NodeContext.AutoPromotion; - return [NodeContext.HasWgePortal, promotionContext]; - } - - get wgePortalQuery() { - const name = this.resource.metadata.name; - const namespace = this.resource.metadata.namespace || 'default'; - - return `pipelines/details/status?kind=Pipeline&name=${name}&namespace=${namespace}`; - } - - - async updateChildren() { - this.children = await this.createEnvNodes(); - this.redraw(); - } - - async createEnvNodes(): Promise { - const envNodes = []; - for(const env of this.resource.spec.environments) { - const envNode = new PipelineEnvironmentNode(env, this.resource); - envNode.updateChildren(); - envNodes.push(envNode); - } - - return envNodes; - } - - - get isManualPromotion() { - return !!this.resource.spec?.promotion?.manual; - } - - get isSuspendIcon(): string { - return this.isManualPromotion ? '⏸ ' : ''; - } - - - async createPromotionNodes() { - return []; - } - -} diff --git a/src/ui/treeviews/nodes/wge/wgeNodes.ts b/src/ui/treeviews/nodes/wge/wgeNodes.ts deleted file mode 100644 index 6d2bc443..00000000 --- a/src/ui/treeviews/nodes/wge/wgeNodes.ts +++ /dev/null @@ -1,106 +0,0 @@ - -import { ToolkitObject } from 'types/flux/object'; -import { NodeContext } from 'types/nodeContext'; -import { themeIcon } from 'ui/icons'; -import { SimpleDataProvider } from 'ui/treeviews/dataProviders/simpleDataProvider'; -import { wgeDataProvider } from 'ui/treeviews/treeViews'; -import { ToolkitNode } from '../toolkitNode'; -import { TreeNode } from '../treeNode'; - -export class WgeNode extends ToolkitNode { - dataProvider!: SimpleDataProvider; - - constructor(object: ToolkitObject) { - super(object, wgeDataProvider); - - // this.label = `${object.kind}: ${object.metadata.name}.${object.metadata.namespace}`; - this.label = `${object.metadata.name}`; - this.makeCollapsible(); - } -} - -export class WgeContainerNode extends TreeNode { - constructor(label: any) { - super(label, wgeDataProvider); - } - - get contexts() { - return [NodeContext.HasWgePortal, 'Container']; - } - - get wgePortalQuery() { - return ''; - } -} - - -export class TemplatesContainerNode extends WgeContainerNode { - constructor() { - super('Templates'); - - this.setIcon(themeIcon('notebook-render-output')); - this.makeCollapsible(); - } - - get wgePortalQuery() { - return 'templates'; - } - - get viewStateKey() { - return 'TemplatesContainer'; - } -} - - -export class CanariesContainerNode extends WgeContainerNode { - constructor() { - super('Canaries'); - - this.setIcon(themeIcon('symbol-null')); - this.makeCollapsible(); - } - - get wgePortalQuery() { - return 'delivery'; - } - - get viewStateKey() { - return 'CanariesContainer'; - } -} - - -export class PipelinesContainerNode extends WgeContainerNode { - constructor() { - super('Pipelines'); - - this.setIcon(themeIcon('rocket')); - this.makeCollapsible(); - } - - get wgePortalQuery() { - return 'pipelines'; - } - - get viewStateKey() { - return 'PipelinesContainer'; - } -} - - -export class GitOpsSetsContainerNode extends WgeContainerNode { - constructor() { - super('GitOpsSets'); - - this.setIcon(themeIcon('outline-view-icon')); - this.makeCollapsible(); - } - - get wgePortalQuery() { - return 'gitopssets'; - } - - get viewStateKey() { - return 'GitOpsSetsContainer'; - } -} diff --git a/src/ui/treeviews/nodes/workload/helmReleaseNode.ts b/src/ui/treeviews/nodes/workload/helmReleaseNode.ts deleted file mode 100644 index 812c57b9..00000000 --- a/src/ui/treeviews/nodes/workload/helmReleaseNode.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getHelmReleaseChildren } from 'cli/kubernetes/kubectlGet'; -import { HelmRelease } from 'types/flux/helmRelease'; -import { SimpleDataProvider } from 'ui/treeviews/dataProviders/simpleDataProvider'; -import { InfoLabel } from 'utils/makeTreeviewInfoNode'; -import { shortenRevision } from 'utils/stringUtils'; -import { groupNodesByNamespace } from 'utils/treeNodeUtils'; -import { AnyResourceNode } from '../anyResourceNode'; -import { TreeNode } from '../treeNode'; -import { WorkloadNode } from './workloadNode'; - -/** - * Defines Helm release tree view item for display in GitOps Workloads tree view. - */ -export class HelmReleaseNode extends WorkloadNode { - resource!: HelmRelease; - dataProvider!: SimpleDataProvider; - - /** - * Creates new helm release tree view item for display. - * @param helmRelease Helm release kubernetes object info. - */ - constructor(helmRelease: HelmRelease, dataProvider: SimpleDataProvider) { - super(helmRelease, dataProvider); - - this.makeCollapsible(); - } - - get revision() { - return shortenRevision(this.resource.status.lastAppliedRevision); - } - - async updateChildren() { - this.children = this.infoNodes(InfoLabel.Loading); - this.redraw(); - - const name = this.resource.metadata.name; - const namespace = this.resource.metadata.namespace || ''; - - const workloadChildren = await getHelmReleaseChildren(name, namespace); - - if (!workloadChildren) { - this.children = this.infoNodes(InfoLabel.FailedToLoad); - this.redraw(); - return; - } - - if (workloadChildren.length === 0) { - this.children = [new TreeNode('No Resources', this.dataProvider)]; - this.redraw(); - return; - } - - const childrenNodes = workloadChildren.map(child => new AnyResourceNode(child, this.dataProvider)); - const [groupedNodes, clusterScopedNodes] = await groupNodesByNamespace(childrenNodes); - this.children = [...groupedNodes, ...clusterScopedNodes]; - - this.redraw(); - } -} diff --git a/src/ui/treeviews/nodes/workload/kustomizationNode.ts b/src/ui/treeviews/nodes/workload/kustomizationNode.ts deleted file mode 100644 index c297574d..00000000 --- a/src/ui/treeviews/nodes/workload/kustomizationNode.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { fluxTools } from 'cli/flux/fluxTools'; -import { Kustomization } from 'types/flux/kustomization'; -import { SimpleDataProvider } from 'ui/treeviews/dataProviders/simpleDataProvider'; -import { InfoLabel } from 'utils/makeTreeviewInfoNode'; -import { shortenRevision } from 'utils/stringUtils'; -import { addFluxTreeToNode } from 'utils/treeNodeUtils'; -import { WorkloadNode } from './workloadNode'; - -/** - * Defines Kustomization tree view item for display in GitOps Workload tree view. - */ -export class KustomizationNode extends WorkloadNode { - resource!: Kustomization; - dataProvider!: SimpleDataProvider; - - /** - * Creates new app kustomization tree view item for display. - * @param kustomization Kustomize kubernetes object info. - */ - constructor(kustomization: Kustomization, dataProvider: SimpleDataProvider) { - super(kustomization, dataProvider); - - this.makeCollapsible(); - } - - get revision() { - return shortenRevision(this.resource.status.lastAppliedRevision); - } - - - async updateChildren() { - this.children = this.infoNodes(InfoLabel.Loading); - this.redraw(); - - const name = this.resource.metadata.name; - const namespace = this.resource.metadata.namespace || ''; - const resourceTree = await fluxTools.tree(name, namespace); - - if (!resourceTree) { - this.children = this.infoNodes(InfoLabel.FailedToLoad); - this.redraw(); - return; - } - - if (!resourceTree.resources) { - this.children = this.infoNodes(InfoLabel.NoResources); - this.redraw(); - return; - } - - this.children = []; - await addFluxTreeToNode(this, resourceTree.resources); - this.redraw(); - } - -} diff --git a/src/ui/treeviews/nodes/workload/workloadNode.ts b/src/ui/treeviews/nodes/workload/workloadNode.ts deleted file mode 100644 index 26ddb3d1..00000000 --- a/src/ui/treeviews/nodes/workload/workloadNode.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FluxWorkloadObject } from 'types/flux/object'; -import { NodeContext } from 'types/nodeContext'; -import { ToolkitNode } from '../toolkitNode'; - -/** - * Base class for all Workload tree view items. - */ -export class WorkloadNode extends ToolkitNode { - resource!: FluxWorkloadObject; - - get contexts() { - return this.resource.spec.suspend ? [NodeContext.Suspend] : [NodeContext.NotSuspend]; - } -} diff --git a/src/ui/treeviews/treeViews.ts b/src/ui/treeviews/treeViews.ts deleted file mode 100644 index c9706264..00000000 --- a/src/ui/treeviews/treeViews.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { TreeItem, TreeItemCollapsibleState, TreeView, window } from 'vscode'; - -import { isAzureProvider } from 'cli/azure/azureTools'; -import { globalState } from 'extension'; -import { Errorable } from 'types/errorable'; -import { TreeViewId } from 'types/extensionIds'; -import { ClusterDataProvider } from './dataProviders/clusterDataProvider'; -import { DocumentationDataProvider } from './dataProviders/documentationDataProvider'; -import { SourceDataProvider } from './dataProviders/sourceDataProvider'; -import { WorkloadDataProvider } from './dataProviders/workloadDataProvider'; -import { ClusterNode } from './nodes/cluster/clusterNode'; - -import { detectClusterProvider } from 'cli/kubernetes/clusterProvider'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { ClusterInfo } from 'types/kubernetes/clusterProvider'; -import { WgeDataProvider } from './dataProviders/wgeDataProvider'; -import { NamespaceNode } from './nodes/namespaceNode'; -import { WgeContainerNode } from './nodes/wge/wgeNodes'; - -export let clusterDataProvider = new ClusterDataProvider(); -export let sourceDataProvider = new SourceDataProvider(); -export let workloadDataProvider = new WorkloadDataProvider(); -export let documentationDataProvider = new DocumentationDataProvider(); -export let wgeDataProvider = new WgeDataProvider(); - -let clusterTreeView: TreeView; -export let sourceTreeView: TreeView; -let workloadTreeView: TreeView; -let documentationTreeView: TreeView; -let wgeTreeView: TreeView; - -/** - * Creates tree views for the GitOps sidebar. - */ -export function createTreeViews() { - // create gitops sidebar tree views - clusterTreeView = window.createTreeView(TreeViewId.ClustersView, { - treeDataProvider: clusterDataProvider, - showCollapseAll: true, - }); - - sourceTreeView = window.createTreeView(TreeViewId.SourcesView, { - treeDataProvider: sourceDataProvider, - showCollapseAll: true, - }); - - workloadTreeView = window.createTreeView(TreeViewId.WorkloadsView, { - treeDataProvider: workloadDataProvider, - showCollapseAll: true, - }); - - // WGE - wgeTreeView = window.createTreeView(TreeViewId.WgeView, { - treeDataProvider: wgeDataProvider, - showCollapseAll: true, - }); - - listenCollapsableState(); - - // create documentation links sidebar tree view - documentationTreeView = window.createTreeView(TreeViewId.DocumentationView, { - treeDataProvider: documentationDataProvider, - showCollapseAll: true, - }); - - documentationDataProvider.reload(); -} - -function listenCollapsableState() { - - // [workloadTreeView, sourceTreeView, wgeTreeView].forEach(treeview => { - // treeview.onDidCollapseElement(e => { - // if (e.element instanceof NamespaceNode) { - // e.element.collapsibleState = TreeItemCollapsibleState.Collapsed; - // const provider = e.element.dataProvider; - // // top-level namespace nodes should get an icon - // const showIcons = provider.nodes.includes(e.element); - // e.element.updateLabel(showIcons); - // provider.redraw(e.element); - // } - // }); - - // treeview.onDidExpandElement(e => { - // if (e.element instanceof NamespaceNode) { - // e.element.collapsibleState = TreeItemCollapsibleState.Expanded; - // const provider = e.element.dataProvider; - // // top-level namespace nodes should get an icon - // const showIcons = provider.nodes.includes(e.element); - // e.element.updateLabel(showIcons); - // provider.redraw(e.element); - // } - // }); - // }); - sourceTreeView.onDidCollapseElement(e => { - if (e.element instanceof NamespaceNode) { - e.element.collapsibleState = TreeItemCollapsibleState.Collapsed; - e.element.updateLabel(); - sourceDataProvider.redraw(e.element); - } - }); - - sourceTreeView.onDidExpandElement(e => { - if (e.element instanceof NamespaceNode) { - e.element.collapsibleState = TreeItemCollapsibleState.Expanded; - e.element.updateLabel(); - sourceDataProvider.redraw(e.element); - } - }); - - workloadTreeView.onDidCollapseElement(e => { - if (e.element instanceof NamespaceNode) { - e.element.collapsibleState = TreeItemCollapsibleState.Collapsed; - // top-level namespace nodes should get an icon - const showIcons = workloadDataProvider.nodes.includes(e.element); - e.element.updateLabel(showIcons); - workloadDataProvider.redraw(e.element); - } - }); - - workloadTreeView.onDidExpandElement(e => { - if (e.element instanceof NamespaceNode) { - e.element.collapsibleState = TreeItemCollapsibleState.Expanded; - // top-level namespace nodes should get an icon - const showIcons = workloadDataProvider.nodes.includes(e.element); - e.element.updateLabel(showIcons); - workloadDataProvider.redraw(e.element); - } - }); - - - wgeTreeView.onDidCollapseElement(e => { - if (e.element instanceof NamespaceNode) { - e.element.collapsibleState = TreeItemCollapsibleState.Collapsed; - // top-level namespace nodes should get an icon - const showIcons = e.element.parent instanceof WgeContainerNode; - e.element.updateLabel(showIcons); - wgeDataProvider.redraw(e.element); - } - }); - - wgeTreeView.onDidExpandElement(e => { - if (e.element instanceof NamespaceNode) { - e.element.collapsibleState = TreeItemCollapsibleState.Expanded; - // top-level namespace nodes should get an icon - const showIcons = e.element.parent instanceof WgeContainerNode; - e.element.updateLabel(showIcons); - wgeDataProvider.redraw(e.element); - } - }); -} - -/** - * Reloads configured clusters tree view via kubectl. - * When an argument is passed - only that tree item - * and its children are updated. - */ -export function reloadClustersTreeView() { - clusterDataProvider.reload(); -} - -/** - * Reloads sources tree view for the selected cluster. - */ -export function reloadSourcesTreeView() { - sourceDataProvider.reload(); -} - -/** - * Reloads workloads tree view for the selected cluster. - */ -export function reloadWorkloadsTreeView() { - workloadDataProvider.reload(); -} - -/** - * Reloads workloads tree view for the selected cluster. - */ -export function reloadWgeTreeView() { - wgeDataProvider.reload(); -} - -/** - * Get info about current cluster/context: - * 1. Cluster name - * 2. Context name - * 3. Detect cluster provider. - */ -export async function getCurrentClusterInfo(): Promise> { - const currentContextName = kubeConfig.getCurrentContext(); - - if (!currentContextName) { - const error = `Failed to get current context ${currentContextName}`; - window.showErrorMessage(error); - return { - succeeded: false, - error: [error], - }; - } - - - let currentClusterName = kubeConfig.getCurrentCluster()?.name; - - // Pick user cluster provider override if defined - const clusterMetadata = globalState.getClusterMetadata(currentClusterName || currentContextName); - const isClusterProviderUserOverride = Boolean(clusterMetadata?.clusterProvider); - const currentClusterProvider = clusterMetadata?.clusterProvider || await detectClusterProvider(currentContextName); - - return { - succeeded: true, - result: { - clusterProvider: currentClusterProvider, - isClusterProviderUserOverride, - isAzure: isAzureProvider(currentClusterProvider), - }, - }; -} - -/** - * Expand, focus or select a tree node inside the Clusters tree view. - * @param clusterNode Target cluster node - */ -export async function revealClusterNode(clusterNode: ClusterNode, { - expand = false, - focus = false, - select = false, -}: { - expand?: boolean; - focus?: boolean; - select?: boolean; -} | undefined = {}) { - return await clusterTreeView.reveal(clusterNode, { - expand, - focus, - select, - }); -} diff --git a/src/ui/webviews/configureGitOps/lib/createGeneric.ts b/src/ui/webviews/configureGitOps/lib/createGeneric.ts deleted file mode 100644 index 85a06e39..00000000 --- a/src/ui/webviews/configureGitOps/lib/createGeneric.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { fluxTools } from 'cli/flux/fluxTools'; -import { showDeployKeyNotificationIfNeeded } from 'commands/createSource'; -import { refreshAllTreeViewsCommand } from 'commands/refreshTreeViews'; -import { telemetry } from 'extension'; -import { TelemetryEvent } from 'types/telemetryEventNames'; -import { reloadSourcesTreeView } from 'ui/treeviews/treeViews'; -import { ParamsDictionary } from 'utils/typeUtils'; - -export async function createConfigurationGeneric(data: ParamsDictionary) { - telemetry.send(TelemetryEvent.CreateSource, { - kind: data.source?.kind, - }); - - - if(data.source) { - const deployKey = await fluxTools.createSource(data.source); - showDeployKeyNotificationIfNeeded(data.source.url, deployKey); - setTimeout(() => { - // Wait a bit for the repository to have a failed state in case of SSH url - reloadSourcesTreeView(); - }, 1000); - - } - - if(data.kustomization) { - await fluxTools.createKustomization(data.kustomization); - } - - refreshAllTreeViewsCommand(); -} diff --git a/src/utils/asAbsolutePath.ts b/src/utils/asAbsolutePath.ts deleted file mode 100644 index aa30d0a3..00000000 --- a/src/utils/asAbsolutePath.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Uri } from 'vscode'; - -import { extensionContext } from 'extension'; - -export function asAbsolutePath(relativePath: string): Uri { - return Uri.file(extensionContext.asAbsolutePath(relativePath)); -} diff --git a/src/utils/fsUtils.ts b/src/utils/fsUtils.ts index 0e1e37dc..f9e42629 100644 --- a/src/utils/fsUtils.ts +++ b/src/utils/fsUtils.ts @@ -3,9 +3,8 @@ import extractZip from 'extract-zip'; import fs from 'fs'; import https from 'https'; import path from 'path'; - -import * as shell from 'cli/shell/exec'; -import { Errorable } from 'types/errorable'; +import { Errorable } from '../errorable'; +import { shell } from '../shell'; /** * Wrap file path in quotes depending on the user os. diff --git a/src/utils/getResourceUri.ts b/src/utils/getResourceUri.ts deleted file mode 100644 index 031ed524..00000000 --- a/src/utils/getResourceUri.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Uri } from 'vscode'; -import { KubernetesFileSchemes } from 'types/kubernetes/kubernetesFileSchemes'; - -/** - * Gets resource Uri for loading kubernetes resource config in vscode editor. - * - * @see https://github.com/Azure/vscode-kubernetes-tools/blob/master/src/kuberesources.virtualfs.ts - * - * @param namespace Resource namespace. - * @param resourceName Resource name. - * @param documentFormat Resource document format. - * @param action Resource Uri action. - * @returns - */ - -export function getResourceUri( - namespace: string | null | undefined, - resourceName: string | undefined, - documentFormat: string, - action?: string, -): Uri { - - // determine resource file extension - let fileExtension = ''; - if (documentFormat !== '') { - fileExtension = `.${documentFormat}`; - } - - // create virtual document file name with extension - const documentName = `${resourceName?.replace('/', '-')}${fileExtension}`; - - // determine virtual resource file scheme - let scheme = KubernetesFileSchemes.Resource; - if (action === 'describe') { - scheme = KubernetesFileSchemes.ReadonlyResource; - } - - // determine virtual resource file authority - let authority: string = KubernetesFileSchemes.KubectlResource; - if (action === 'describe') { - authority = KubernetesFileSchemes.DescribeResource; - } - - // set namespace query param - let namespaceQuery = ''; - if (namespace) { - namespaceQuery = `ns=${namespace}&`; - } - - // create resource url - const nonce: number = new Date().getTime(); - const url = `${scheme}://${authority}/${documentName}?${namespaceQuery}value=${resourceName}&_=${nonce}`; - - // create resource uri - return Uri.parse(url); -} diff --git a/src/utils/jsonUtils.ts b/src/utils/jsonUtils.ts index 027b9513..312f7d7d 100644 --- a/src/utils/jsonUtils.ts +++ b/src/utils/jsonUtils.ts @@ -1,7 +1,6 @@ import { window } from 'vscode'; - -import { telemetry } from 'extension'; -import { TelemetryError } from 'types/telemetryEventNames'; +import { telemetry } from '../extension'; +import { TelemetryErrorEventNames } from '../telemetry'; export function parseJson(jsonString: string): any { let jsonData: any; @@ -10,15 +9,9 @@ export function parseJson(jsonString: string): any { jsonData = JSON.parse(jsonString.trim()); } catch(e: unknown) { window.showErrorMessage(`JSON.parse() failed ${e}`); - telemetry.sendError(TelemetryError.UNCAUGHT_EXCEPTION, new Error('parseJson() failed')); + telemetry.sendError(TelemetryErrorEventNames.UNCAUGHT_EXCEPTION, new Error('parseJson() failed')); return; } return jsonData; } - - -export function parseJsonItems(jsonString: string): T[] { - const result = parseJson(jsonString); - return result?.items || []; -} diff --git a/src/utils/kubeConfigCompare.ts b/src/utils/kubeConfigCompare.ts deleted file mode 100644 index 67b8e84d..00000000 --- a/src/utils/kubeConfigCompare.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as k8s from '@kubernetes/client-node'; -import deepEqual from 'lite-deep-equal'; - -export function kcTextChanged(kc1: k8s.KubeConfig, kc2: k8s.KubeConfig): boolean { - // exportConfig() will omit tokens and certs - return kc1.exportConfig() !== kc2.exportConfig(); -} - -export function kcContextsListChanged(kc1: k8s.KubeConfig, kc2: k8s.KubeConfig): boolean { - const contexts1 = kc1.getContexts(); - const contexts2 = kc2.getContexts(); - - return !deepEqual(contexts1, contexts2); -} - -export function kcCurrentContextChanged(kc1: k8s.KubeConfig, kc2: k8s.KubeConfig): boolean { - const context1 = kc1.getContextObject(kc1.getCurrentContext()); - const context2 = kc2.getContextObject(kc2.getCurrentContext()); - - const cluster1 = kc1.getCurrentCluster(); - const cluster2 = kc2.getCurrentCluster(); - - const user1 = kc1.getCurrentUser(); - const user2 = kc2.getCurrentUser(); - - return !deepEqual(context1, context2) || !deepEqual(cluster1, cluster2) || !deepEqual(user1, user2); -} diff --git a/src/utils/makeTreeviewInfoNode.ts b/src/utils/makeTreeviewInfoNode.ts deleted file mode 100644 index 04a1a5df..00000000 --- a/src/utils/makeTreeviewInfoNode.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { CommonIcon } from 'ui/icons'; -import { SimpleDataProvider } from 'ui/treeviews/dataProviders/simpleDataProvider'; -import { TreeNode } from '../ui/treeviews/nodes/treeNode'; - - -export enum InfoLabel { - FailedToLoad, - NoResources, - Loading, - LoadingApi, - ClusterUnreachable, -} - -export function infoNodes(type: InfoLabel, provider: SimpleDataProvider) { - return [infoNode(type, provider)]; -} - -export function infoNode(type: InfoLabel, provider: SimpleDataProvider) { - let node; - - switch(type) { - case InfoLabel.FailedToLoad: - node = new TreeNode('Failed to load', provider); - node.setCommonIcon(CommonIcon.Disconnected); - return node; - case InfoLabel.NoResources: - return new TreeNode('No Resources', provider); - case InfoLabel.Loading: - node = new TreeNode('Loading...', provider); - node.setCommonIcon(CommonIcon.Loading); - return node; - case InfoLabel.LoadingApi: - node = new TreeNode('Loading API...', provider); - node.setCommonIcon(CommonIcon.Loading); - return node; - case InfoLabel.ClusterUnreachable: - const name = kubeConfig.currentContext; - node = new TreeNode(`Cluster ${name} unreachable`, provider); - node.setCommonIcon(CommonIcon.Disconnected); - return node; - } -} - diff --git a/src/utils/markdownUtils.ts b/src/utils/markdownUtils.ts index 0ae346a9..441ef57a 100644 --- a/src/utils/markdownUtils.ts +++ b/src/utils/markdownUtils.ts @@ -1,129 +1,99 @@ -import * as k8s from '@kubernetes/client-node'; import { MarkdownString } from 'vscode'; - -import { GitOpsTemplate } from 'types/flux/gitOpsTemplate'; -import { ToolkitObject } from 'types/flux/object'; -import { Pipeline } from 'types/flux/pipeline'; -import { Deployment, Kind, Namespace } from 'types/kubernetes/kubernetesTypes'; +import { Bucket } from '../kubernetes/types/flux/bucket'; +import { GitRepository } from '../kubernetes/types/flux/gitRepository'; +import { OCIRepository } from '../kubernetes/types/flux/ociRepository'; +import { HelmRelease } from '../kubernetes/types/flux/helmRelease'; +import { HelmRepository } from '../kubernetes/types/flux/helmRepository'; +import { KubernetesCluster, KubernetesContextWithCluster } from '../kubernetes/types/kubernetesConfig'; +import { Deployment, KubernetesObjectKinds, Namespace } from '../kubernetes/types/kubernetesTypes'; +import { Kustomize } from '../kubernetes/types/flux/kustomize'; import { shortenRevision } from './stringUtils'; +import { GitOpsTemplate } from '../kubernetes/types/flux/gitOpsTemplate'; -export type KnownTreeNodeResources = Namespace | Deployment | ToolkitObject | GitOpsTemplate | Pipeline; - - -export function createContextMarkdownTable(context: k8s.Context, cluster?: k8s.Cluster): MarkdownString { - const markdown = new MarkdownString(undefined, true); - markdown.isTrusted = true; - // Create table header - markdown.appendMarkdown('Property | Value\n'); - markdown.appendMarkdown(':--- | :---\n'); - - // Cluster type is incompatible with the rest. Handle it first. - createMarkdownTableRow('context name', context.name, markdown); - createMarkdownTableRow('cluster name', cluster?.name, markdown); - createMarkdownTableRow('cluster.server', cluster?.server, markdown); - return markdown; -} +export type KnownTreeNodeResources = KubernetesContextWithCluster | Namespace | Bucket | GitRepository | OCIRepository | HelmRepository | HelmRelease | Kustomize | Deployment | GitOpsTemplate; /** * Create markdown table for tree view item hovers. * 2 clumns, left aligned. - * @param obj Standard kubernetes object + * @param kubernetesObject Standard kubernetes object * @returns vscode MarkdownString object */ -export function createMarkdownTable(obj: KnownTreeNodeResources): MarkdownString { +export function createMarkdownTable(kubernetesObject: KnownTreeNodeResources): MarkdownString { const markdown = new MarkdownString(undefined, true); markdown.isTrusted = true; // Create table header markdown.appendMarkdown('Property | Value\n'); markdown.appendMarkdown(':--- | :---\n'); + // Cluster type is incompatible with the rest. Handle it first. + if ('context' in kubernetesObject) { + createMarkdownTableRow('context name', kubernetesObject.name, markdown); + createMarkdownTableRow('cluster name', kubernetesObject.context.clusterInfo?.name, markdown); + createMarkdownTableRow('cluster.server', kubernetesObject.context.clusterInfo?.cluster?.server, markdown); + createMarkdownTableRow('cluster.certificate-authority', kubernetesObject.context.clusterInfo?.cluster?.['certificate-authority'], markdown); + createMarkdownTableRow('cluster.certificate-authority-data', kubernetesObject.context.clusterInfo?.cluster?.['certificate-authority-data'], markdown); + return markdown; + } + // Should exist on every object - createMarkdownTableRow('kind', obj.kind, markdown); - createMarkdownTableRow('name', obj.metadata.name, markdown); - createMarkdownTableRow('namespace', obj.metadata.namespace, markdown); + createMarkdownTableRow('kind', kubernetesObject.kind, markdown); + createMarkdownTableRow('name', kubernetesObject.metadata?.name, markdown); + createMarkdownTableRow('namespace', kubernetesObject.metadata?.namespace, markdown); // Object-specific properties - if (obj.kind === Kind.GitRepository) { - createMarkdownTableRow('spec.suspend', obj.spec?.suspend === undefined ? false : obj.spec?.suspend, markdown); - createMarkdownTableRow('spec.url', obj.spec?.url, markdown); - createMarkdownTableRow('spec.ref.commit', obj.spec?.ref?.commit, markdown); - createMarkdownTableRow('spec.ref.branch', obj.spec?.ref?.branch, markdown); - createMarkdownTableRow('spec.ref.tag', obj.spec?.ref?.tag, markdown); - createMarkdownTableRow('spec.ref.semver', obj.spec?.ref?.semver, markdown); - } else if (obj.kind === Kind.OCIRepository) { - createMarkdownTableRow('spec.url', obj.spec?.url, markdown); - createMarkdownTableRow('spec.ref.digest', obj.spec?.ref?.digest, markdown); - createMarkdownTableRow('spec.ref.semver', obj.spec?.ref?.semver, markdown); - createMarkdownTableRow('spec.ref.tag', obj.spec?.ref?.tag, markdown); - } else if (obj.kind === Kind.HelmRepository) { - createMarkdownTableRow('spec.url', obj.spec?.url, markdown); - createMarkdownTableRow('spec.type', obj.spec?.type, markdown); - } else if (obj.kind === Kind.Bucket) { - createMarkdownTableRow('spec.bucketName', obj.spec?.bucketName, markdown); - createMarkdownTableRow('spec.endpoint', obj.spec?.endpoint, markdown); - createMarkdownTableRow('spec.provider', obj.spec?.provider, markdown); - createMarkdownTableRow('spec.insecure', obj.spec?.insecure, markdown); - } else if (obj.kind === Kind.Kustomization) { - const sourceRef = `${obj.spec?.sourceRef?.kind}/${obj.spec?.sourceRef?.name}.${obj.spec?.sourceRef?.namespace || obj.metadata.namespace}`; + if (kubernetesObject.kind === KubernetesObjectKinds.GitRepository) { + createMarkdownTableRow('spec.suspend', kubernetesObject.spec?.suspend === undefined ? false : kubernetesObject.spec?.suspend, markdown); + createMarkdownTableRow('spec.url', kubernetesObject.spec?.url, markdown); + createMarkdownTableRow('spec.ref.commit', kubernetesObject.spec?.ref?.commit, markdown); + createMarkdownTableRow('spec.ref.branch', kubernetesObject.spec?.ref?.branch, markdown); + createMarkdownTableRow('spec.ref.tag', kubernetesObject.spec?.ref?.tag, markdown); + createMarkdownTableRow('spec.ref.semver', kubernetesObject.spec?.ref?.semver, markdown); + } else if (kubernetesObject.kind === KubernetesObjectKinds.OCIRepository) { + createMarkdownTableRow('spec.url', kubernetesObject.spec?.url, markdown); + createMarkdownTableRow('spec.ref.digest', kubernetesObject.spec?.ref?.digest, markdown); + createMarkdownTableRow('spec.ref.semver', kubernetesObject.spec?.ref?.semver, markdown); + createMarkdownTableRow('spec.ref.tag', kubernetesObject.spec?.ref?.tag, markdown); + } else if (kubernetesObject.kind === KubernetesObjectKinds.HelmRepository) { + createMarkdownTableRow('spec.url', kubernetesObject.spec?.url, markdown); + createMarkdownTableRow('spec.type', kubernetesObject.spec?.type, markdown); + } else if (kubernetesObject.kind === KubernetesObjectKinds.Bucket) { + createMarkdownTableRow('spec.bucketName', kubernetesObject.spec?.bucketName, markdown); + createMarkdownTableRow('spec.endpoint', kubernetesObject.spec?.endpoint, markdown); + createMarkdownTableRow('spec.provider', kubernetesObject.spec?.provider, markdown); + createMarkdownTableRow('spec.insecure', kubernetesObject.spec?.insecure, markdown); + } else if (kubernetesObject.kind === KubernetesObjectKinds.Kustomization) { + const sourceRef = `${kubernetesObject.spec?.sourceRef?.kind}/${kubernetesObject.spec?.sourceRef?.name}.${kubernetesObject.spec?.sourceRef?.namespace || kubernetesObject.metadata?.namespace}`; createMarkdownTableRow('source', sourceRef, markdown); - createMarkdownTableRow('spec.suspend', obj.spec?.suspend === undefined ? false : obj.spec?.suspend, markdown); - createMarkdownTableRow('spec.prune', obj.spec?.prune, markdown); - createMarkdownTableRow('spec.force', obj.spec?.force, markdown); - createMarkdownTableRow('spec.path', obj.spec?.path, markdown); - } else if (obj.kind === Kind.HelmRelease) { - const sourceRef = `${obj.spec?.chart?.spec?.sourceRef?.kind}/${obj.spec?.chart?.spec?.sourceRef?.name}.${obj.spec?.chart?.spec?.sourceRef?.namespace || obj.metadata.namespace}`; + createMarkdownTableRow('spec.suspend', kubernetesObject.spec?.suspend === undefined ? false : kubernetesObject.spec?.suspend, markdown); + createMarkdownTableRow('spec.prune', kubernetesObject.spec?.prune, markdown); + createMarkdownTableRow('spec.force', kubernetesObject.spec?.force, markdown); + createMarkdownTableRow('spec.path', kubernetesObject.spec?.path, markdown); + } else if (kubernetesObject.kind === KubernetesObjectKinds.HelmRelease) { + const sourceRef = `${kubernetesObject.spec?.chart?.spec?.sourceRef?.kind}/${kubernetesObject.spec?.chart?.spec?.sourceRef?.name}.${kubernetesObject.spec?.chart?.spec?.sourceRef?.namespace || kubernetesObject.metadata?.namespace}`; createMarkdownTableRow('source', sourceRef, markdown); - createMarkdownTableRow('spec.suspend', obj.spec?.suspend === undefined ? false : obj.spec?.suspend, markdown); - createMarkdownTableRow('spec.chart.spec.chart', obj.spec?.chart?.spec?.chart, markdown); - - createMarkdownTableRow('spec.chart.spec.version', obj.spec?.chart?.spec?.version, markdown); - } else if (obj.kind === Kind.Canary) { - createMarkdownTableRow('spec.suspend', obj.spec?.suspend === undefined ? false : obj.spec?.suspend, markdown); - - createMarkdownTableRow('phase', obj.status.phase, markdown); - createMarkdownTableRow('failedChecks', obj.status.failedChecks, markdown); - createMarkdownTableRow('canaryWeight', obj.status.canaryWeight, markdown); - createMarkdownTableRow('iterations', obj.status.iterations, markdown); - createMarkdownTableRow('lastAppliedSpec', obj.status.lastAppliedSpec, markdown); - createMarkdownTableRow('lastPromotedSpec', obj.status.lastPromotedSpec, markdown); - createMarkdownTableRow('lastTransitionTime', obj.status.lastTransitionTime, markdown); - } else if (obj.kind === Kind.Deployment) { - createMarkdownTableRow('spec.paused', obj.spec?.paused, markdown); - createMarkdownTableRow('spec.minReadySeconds', obj.spec?.minReadySeconds, markdown); - createMarkdownTableRow('spec.progressDeadlineSeconds', obj.spec?.progressDeadlineSeconds, markdown); - } else if (obj.kind === Kind.Pipeline) { - if(obj.spec?.promotion?.manual) { - createMarkdownTableRow('promotion.manual', obj.spec?.promotion?.manual, markdown); - } - - if(obj.spec?.promotion?.strategy.notification) { - createMarkdownTableRow('promotion.strategy.notification', true, markdown); - } - - const strategy = obj.spec?.promotion?.strategy as any; - if(strategy['pull-request']) { - createMarkdownTableRow('pull-request.type', strategy['pull-request'].type, markdown); - createMarkdownTableRow('pull-request.url', strategy['pull-request'].url, markdown); - createMarkdownTableRow('pull-request.baseBranch', strategy['pull-request'].baseBranch, markdown); - } + createMarkdownTableRow('spec.suspend', kubernetesObject.spec?.suspend === undefined ? false : kubernetesObject.spec?.suspend, markdown); + createMarkdownTableRow('spec.chart.spec.chart', kubernetesObject.spec?.chart?.spec?.chart, markdown); - createMarkdownTableRow('spec.appRef.kind', obj.spec?.appRef.kind, markdown); - createMarkdownTableRow('spec.appRef.name', obj.spec?.appRef.name, markdown); + createMarkdownTableRow('spec.chart.spec.version', kubernetesObject.spec?.chart?.spec?.version, markdown); + } else if (kubernetesObject.kind === KubernetesObjectKinds.Deployment) { + createMarkdownTableRow('spec.paused', kubernetesObject.spec?.paused, markdown); + createMarkdownTableRow('spec.minReadySeconds', kubernetesObject.spec?.minReadySeconds, markdown); + createMarkdownTableRow('spec.progressDeadlineSeconds', kubernetesObject.spec?.progressDeadlineSeconds, markdown); } // Should exist on multiple objects - if(obj.spec) { - if ('interval' in obj.spec) { - createMarkdownTableRow('spec.interval', obj.spec?.interval, markdown); + if(kubernetesObject.spec) { + if ('interval' in kubernetesObject.spec) { + createMarkdownTableRow('spec.interval', kubernetesObject.spec?.interval, markdown); } - if ('timeout' in obj.spec) { - createMarkdownTableRow('spec.timeout', obj.spec?.timeout, markdown); + if ('timeout' in kubernetesObject.spec) { + createMarkdownTableRow('spec.timeout', kubernetesObject.spec?.timeout, markdown); } } - const fluxStatus = obj.status as any; + const fluxStatus = kubernetesObject.status as any; if(fluxStatus?.lastAttemptedRevision) { createMarkdownTableRow('attempted', shortenRevision(fluxStatus.lastAttemptedRevision), markdown); @@ -134,8 +104,8 @@ export function createMarkdownTable(obj: KnownTreeNodeResources): MarkdownString } - if(obj.status?.conditions) { - const conditions = obj.status.conditions as any[]; + if(kubernetesObject.status?.conditions) { + const conditions = kubernetesObject.status.conditions as any[]; for (const c of conditions) { if(c.type === 'SourceVerified' && c.status === 'True') { const message = `${c.message.replace('verified signature of revision', 'verified').slice(0, 48)}...`; diff --git a/src/utils/namespacedFluxObject.ts b/src/utils/namespacedFluxObject.ts deleted file mode 100644 index bc8f0a20..00000000 --- a/src/utils/namespacedFluxObject.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FluxSourceObject, FluxWorkloadObject } from '../types/flux/object'; - -export function namespacedFluxObject(resource?: FluxSourceObject | FluxWorkloadObject): string | undefined { - if (resource) { - return `${resource.kind}/${resource.metadata.name}.${resource.metadata.namespace}`; - } -} - -export function splitNamespacedFluxObject(fullname: string) { - const [kind, nameNs] = fullname.split('/'); - const [name, namespace] = nameNs.split('.'); - return { kind, name, namespace }; -} diff --git a/src/utils/treeNodeUtils.ts b/src/utils/treeNodeUtils.ts deleted file mode 100644 index 015dcb7a..00000000 --- a/src/utils/treeNodeUtils.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { getCachedNamespaces } from 'cli/kubernetes/kubectlGetNamespace'; -import { FluxTreeResources } from 'types/fluxCliTypes'; -import { Namespace } from 'types/kubernetes/kubernetesTypes'; -import { AnyResourceNode } from 'ui/treeviews/nodes/anyResourceNode'; -import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { NamespaceNode } from '../ui/treeviews/nodes/namespaceNode'; -import { TreeNode } from '../ui/treeviews/nodes/treeNode'; - - -export async function addFluxTreeToNode(node: TreeNode, resourceTree: FluxTreeResources[], parentNamespace = '') { - const nodes: TreeNode[] = []; - for (const resource of resourceTree) { - // Nested items have empty namespace https://github.com/fluxcd/flux2/issues/2149 - const namespace = resource.resource.Namespace || parentNamespace; - - const childNode = new AnyResourceNode({ - kind: resource.resource.GroupKind.Kind, - metadata: { - name: resource.resource.Name, - namespace, - uid: JSON.stringify(resource.resource), // fake UID, we're using for treeview indexing only - }, - - }, node.dataProvider!); - - nodes.push(childNode); - - if (resource.resources && resource.resources.length) { - addFluxTreeToNode(childNode, resource.resources, namespace); - } - } - - const [groupedNodes, clusterScopedNodes] = await groupNodesByNamespace(nodes); - clusterScopedNodes.forEach(csNode => node.addChild(csNode)); - groupedNodes.forEach(nsNode => node.addChild(nsNode)); -} - -// returns grouped by namespace, and ugroupable (cluster scoped) nodes -export async function groupNodesByNamespace(nodes: TreeNode[], expandAll = false, withIcons = false): Promise<[NamespaceNode[], TreeNode[]]> { - const namespaces: Namespace[] = getCachedNamespaces(); - const namespaceNodes: NamespaceNode[] = []; - - namespaces.forEach(ns => { - const nsName = ns.metadata.name!; - - const nsChildNodes = filterNodesForNamespace(nodes, nsName); - if (nsChildNodes.length > 0) { - const nsNode = new NamespaceNode(ns, nsChildNodes[0].dataProvider); - nsChildNodes.forEach(childNode => { - // Don't add the namespace node as a child of itself - if(!(childNode.resource.kind === 'Namespace' && childNode.resource.metadata.name === nsName)) { - nsNode.addChild(childNode); - } - }); - nsNode.collapsibleState = expandAll ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed; - nsNode.updateLabel(withIcons); - - namespaceNodes.push(nsNode); - } - }); - - const clusterScopedNodes = nodes.filter(node => !node.resource.metadata.namespace && node.resource.kind !== 'Namespace'); - return [namespaceNodes, clusterScopedNodes]; -} - -function filterNodesForNamespace(nodes: TreeNode[], namespace: string): TreeNode[] { - const belongsToNamespace = (node: TreeNode) => node.resource.metadata.namespace === namespace; - const isNamespace = (node: TreeNode) => node.resource.kind === 'Namespace' && node.resource.metadata.name === namespace; - - return nodes.filter(node => belongsToNamespace(node) || isNamespace(node)); -} - - -export function sortNodes(nodes?: TreeItem[] | null) { - if(nodes) { - nodes.sort((a, b) => { - if(a.label && b.label) { - const al = typeof a.label === 'string' ? a.label : a.label.label; - const bl = typeof b.label === 'string' ? b.label : b.label.label; - return al.localeCompare(bl); - } - return 0; - }); - } -} - diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 2a1bf24a..7a3e5428 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -6,8 +6,3 @@ export async function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } - -// small timestamp for debug -export function ts() { - return Date().slice(19, 24); -} diff --git a/src/views/dataProviders/clusterDataProvider.ts b/src/views/dataProviders/clusterDataProvider.ts new file mode 100644 index 00000000..5c2f2937 --- /dev/null +++ b/src/views/dataProviders/clusterDataProvider.ts @@ -0,0 +1,169 @@ +import { TreeItem, window } from 'vscode'; +import { failed } from '../../errorable'; +import { fluxTools } from '../../flux/fluxTools'; +import { kubernetesTools } from '../../kubernetes/kubernetesTools'; +import { statusBar } from '../../statusBar'; +import { ContextTypes, setVSCodeContext } from '../../vscodeContext'; +import { ClusterContextNode } from '../nodes/clusterContextNode'; +import { ClusterDeploymentNode } from '../nodes/clusterDeploymentNode'; +import { TreeNode } from '../nodes/treeNode'; +import { refreshClustersTreeView, revealClusterNode } from '../treeViews'; +import { DataProvider } from './dataProvider'; + +/** + * Defines Clusters data provider for loading configured kubernetes clusters + * and contexts in GitOps Clusters tree view. + */ +export class ClusterDataProvider extends DataProvider { + + /** + * Keep a reference to all the nodes in the Clusters Tree View. + */ + private clusterNodes: ClusterContextNode[] = []; + + /** + * Check if the cluster node exists or not. + */ + public includesTreeNode(treeItem: TreeItem, clusterNodes: TreeNode[] = this.clusterNodes) { + for (const clusterNode of clusterNodes) { + if (treeItem === clusterNode) { + return true; + } + const includesInNested = this.includesTreeNode(treeItem, clusterNode.children); + if (includesInNested) { + return true; + } + } + return false; + } + + /** + * Creates Clusters tree view items from local kubernetes config. + */ + async buildTree(): Promise { + + setVSCodeContext(ContextTypes.FailedToLoadClusterContexts, false); + setVSCodeContext(ContextTypes.NoClusters, false); + setVSCodeContext(ContextTypes.LoadingClusters, true); + statusBar.startLoadingTree(); + this.clusterNodes = []; + + const [contextsResult, currentContextResult] = await Promise.all([ + kubernetesTools.getContexts(), + kubernetesTools.getCurrentContext(), + ]); + + if (failed(contextsResult)) { + setVSCodeContext(ContextTypes.NoClusters, false); + setVSCodeContext(ContextTypes.FailedToLoadClusterContexts, true); + setVSCodeContext(ContextTypes.LoadingClusters, false); + statusBar.stopLoadingTree(); + window.showErrorMessage(`Failed to get contexts: ${contextsResult.error[0]}`); + return []; + } + + const clusterNodes: ClusterContextNode[] = []; + let currentContextTreeItem: ClusterContextNode | undefined; + + let currentContext = ''; + if (failed(currentContextResult)) { + window.showErrorMessage(`Failed to get current context: ${currentContextResult.error[0]}`); + } else { + currentContext = currentContextResult.result; + } + + if (contextsResult.result.length === 0) { + setVSCodeContext(ContextTypes.NoClusters, true); + return []; + } + + for (const cluster of contextsResult.result) { + const clusterNode = new ClusterContextNode(cluster); + if (cluster.name === currentContext) { + clusterNode.isCurrent = true; + currentContextTreeItem = clusterNode; + clusterNode.makeCollapsible(); + // load flux system deployments + const fluxControllers = await kubernetesTools.getFluxControllers(); + if (fluxControllers) { + clusterNode.expand(); + revealClusterNode(clusterNode, { + expand: true, + }); + for (const deployment of fluxControllers.items) { + clusterNode.addChild(new ClusterDeploymentNode(deployment)); + } + } + } + if(clusterNode.isCurrent) { + this.updateClusterContext(clusterNode); + } + clusterNodes.push(clusterNode); + } + + // Update async status of the deployments (flux commands take a while to run) + this.updateDeploymentStatus(currentContextTreeItem); + // Update async cluster context/icons + // this.updateClusterContexts(clusterNodes); + + statusBar.stopLoadingTree(); + setVSCodeContext(ContextTypes.LoadingClusters, false); + this.clusterNodes = clusterNodes; + + return clusterNodes; + } + + /** + * Update deployment status for flux controllers. + * Get status from running flux commands instead of kubectl. + */ + async updateDeploymentStatus(clusterNode?: ClusterContextNode) { + if (!clusterNode || clusterNode.children.length === 0) { + return; + } + const fluxCheckResult = await fluxTools.check(clusterNode.contextName); + if (!fluxCheckResult) { + return; + } + + // Match controllers fetched with flux with controllers + // fetched with kubectl and update tree nodes. + for (const clusterController of (clusterNode.children as ClusterDeploymentNode[])) { + for (const controller of fluxCheckResult.controllers) { + const clusterControllerName = clusterController.resource.metadata.name?.trim(); + const deploymentName = controller.name.trim(); + + if (clusterControllerName === deploymentName) { + clusterController.description = controller.status; + if (controller.success) { + clusterController.setStatus('success'); + } else { + clusterController.setStatus('failure'); + } + } + } + refreshClustersTreeView(clusterController); + } + } + + /** + * Update cluster context for all cluster nodes one by one. + * @param clusterNodes all cluster nodes in this tree view. + */ + // TODO: FIXME: calling this is a bad idea with more than 10-100 contexts + async updateClusterContexts(clusterNodes: ClusterContextNode[]) { + await Promise.all(clusterNodes.map(async clusterNode => { + await clusterNode.updateNodeContext(); + refreshClustersTreeView(clusterNode); + })); + } + + /** + * Update cluster context for a single cluster node. + * @param clusterNode Usually the selected clusterNode. + */ + async updateClusterContext(clusterNode: ClusterContextNode) { + await clusterNode.updateNodeContext(); + refreshClustersTreeView(clusterNode); + } +} diff --git a/src/views/dataProviders/dataProvider.ts b/src/views/dataProviders/dataProvider.ts new file mode 100644 index 00000000..e07520fa --- /dev/null +++ b/src/views/dataProviders/dataProvider.ts @@ -0,0 +1,93 @@ +import { Event, EventEmitter, TreeDataProvider, TreeItem } from 'vscode'; +import { Namespace } from '../../kubernetes/types/kubernetesTypes'; +import { NamespaceNode } from '../nodes/namespaceNode'; +import { TreeNode } from '../nodes/treeNode'; + +/** + * Defines tree view data provider base class for all GitOps tree views. + */ +export class DataProvider implements TreeDataProvider { + private treeItems: TreeItem[] | null = null; + private _onDidChangeTreeData: EventEmitter = new EventEmitter(); + readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; + + /** + * Reloads tree view item and its children. + * @param treeItem Tree item to refresh. + */ + public refresh(treeItem?: TreeItem) { + if (!treeItem) { + // Only clear all root nodes when no node was passed + this.treeItems = null; + } + this._onDidChangeTreeData.fire(treeItem); + } + + /** + * Gets tree view item for the specified tree element. + * @param element Tree element. + * @returns Tree view item. + */ + public getTreeItem(element: TreeItem): TreeItem { + return element; + } + + /** + * Gets tree element parent. + * @param element Tree item to get parent for. + * @returns Parent tree item or null for the top level nodes. + */ + public getParent(element: TreeItem): TreeItem | null { + if (element instanceof TreeNode && element.parent) { + return element.parent; + } + return null; + } + + /** + * Gets children for the specified tree element. + * Creates new tree view items for the root node. + * @param element The tree element to get children for. + * @returns Tree element children or empty array. + */ + public async getChildren(element?: TreeItem): Promise { + if (!this.treeItems) { + this.treeItems = await this.buildTree(); + } + + if (element instanceof TreeNode) { + return element.children; + } + + if (!element && this.treeItems) { + return this.treeItems; + } + + return []; + } + + /** + * Creates initial tree view items collection. + * @returns + */ + buildTree(): Promise { + return Promise.resolve([]); + } + + groupByNamespace(namespaces: Namespace[], nodes: TreeNode[]): NamespaceNode[] { + const namespaceNodes: NamespaceNode[] = []; + + namespaces.forEach(ns => { + const name = ns.metadata.name; + + const nsChildNodes = nodes.filter(node => node.resource?.metadata?.namespace === name); + if(nsChildNodes.length > 0) { + const nsNode = new NamespaceNode(ns); + nsChildNodes.forEach(childNode => nsNode.addChild(childNode)); + namespaceNodes.push(nsNode); + } + }); + + return namespaceNodes; + } +} diff --git a/src/ui/treeviews/dataProviders/documentationDataProvider.ts b/src/views/dataProviders/documentationDataProvider.ts similarity index 73% rename from src/ui/treeviews/dataProviders/documentationDataProvider.ts rename to src/views/dataProviders/documentationDataProvider.ts index 7eefb437..2870220b 100644 --- a/src/ui/treeviews/dataProviders/documentationDataProvider.ts +++ b/src/views/dataProviders/documentationDataProvider.ts @@ -1,20 +1,16 @@ import { documentationLinks } from '../documentationConfig'; import { DocumentationNode } from '../nodes/documentationNode'; -import { SimpleDataProvider } from './simpleDataProvider'; +import { DataProvider } from './dataProvider'; /** * Defines data provider for Documentation tree view. */ -export class DocumentationDataProvider extends SimpleDataProvider { - - protected async getRootNodes() { - return this.nodes; - } +export class DocumentationDataProvider extends DataProvider { /** * Creates documentation tree view from documenation links config. */ - async loadRootNodes() { + async buildTree(): Promise { const treeNodes: DocumentationNode[] = []; for (const link of documentationLinks) { diff --git a/src/views/dataProviders/sourceDataProvider.ts b/src/views/dataProviders/sourceDataProvider.ts new file mode 100644 index 00000000..a735cce2 --- /dev/null +++ b/src/views/dataProviders/sourceDataProvider.ts @@ -0,0 +1,73 @@ +import { ContextTypes, setVSCodeContext } from '../../vscodeContext'; +import { kubernetesTools } from '../../kubernetes/kubernetesTools'; +import { statusBar } from '../../statusBar'; +import { BucketNode } from '../nodes/bucketNode'; +import { GitRepositoryNode } from '../nodes/gitRepositoryNode'; +import { OCIRepositoryNode } from '../nodes/ociRepositoryNode'; +import { HelmRepositoryNode } from '../nodes/helmRepositoryNode'; +import { SourceNode } from '../nodes/sourceNode'; +import { DataProvider } from './dataProvider'; +import { sortByMetadataName } from '../../kubernetes/kubernetesUtils'; +import { NamespaceNode } from '../nodes/namespaceNode'; + +/** + * Defines Sources data provider for loading Git/Helm repositories + * and Buckets in GitOps Sources tree view. + */ +export class SourceDataProvider extends DataProvider { + + /** + * Creates Source tree view items for the currently selected kubernetes cluster. + * @returns Source tree view items to display. + */ + async buildTree(): Promise { + statusBar.startLoadingTree(); + + const treeItems: SourceNode[] = []; + + setVSCodeContext(ContextTypes.LoadingSources, true); + + // Fetch all sources asynchronously and at once + const [gitRepositories, ociRepositories, helmRepositories, buckets, namespaces] = await Promise.all([ + kubernetesTools.getGitRepositories(), + kubernetesTools.getOciRepositories(), + kubernetesTools.getHelmRepositories(), + kubernetesTools.getBuckets(), + kubernetesTools.getNamespaces(), + ]); + + // add git repositories to the tree + if (gitRepositories) { + for (const gitRepository of sortByMetadataName(gitRepositories.items)) { + treeItems.push(new GitRepositoryNode(gitRepository)); + } + } + + // add oci repositories to the tree + if (ociRepositories) { + for (const ociRepository of sortByMetadataName(ociRepositories.items)) { + treeItems.push(new OCIRepositoryNode(ociRepository)); + } + } + + // add helm repositores to the tree + if (helmRepositories) { + for (const helmRepository of sortByMetadataName(helmRepositories.items)) { + treeItems.push(new HelmRepositoryNode(helmRepository)); + } + } + + // add buckets to the tree + if (buckets) { + for (const bucket of sortByMetadataName(buckets.items)) { + treeItems.push(new BucketNode(bucket)); + } + } + + setVSCodeContext(ContextTypes.LoadingSources, false); + setVSCodeContext(ContextTypes.NoSources, treeItems.length === 0); + statusBar.stopLoadingTree(); + + return this.groupByNamespace(namespaces?.items || [], treeItems); + } +} diff --git a/src/views/dataProviders/templateDataProvider.ts b/src/views/dataProviders/templateDataProvider.ts new file mode 100644 index 00000000..1c8781dd --- /dev/null +++ b/src/views/dataProviders/templateDataProvider.ts @@ -0,0 +1,22 @@ +import { kubernetesTools } from '../../kubernetes/kubernetesTools'; +import { sortByMetadataName } from '../../kubernetes/kubernetesUtils'; +import { GitOpsTemplate } from '../../kubernetes/types/flux/gitOpsTemplate'; +import { GitOpsTemplateNode } from '../nodes/gitOpsTemplateNode'; +import { DataProvider } from './dataProvider'; + +export class TemplateDataProvider extends DataProvider { + + async buildTree(): Promise { + const nodes = []; + + const templates = await kubernetesTools.getGitOpsTemplates(); + + if(templates) { + for (const template of sortByMetadataName(templates.items)) { + nodes.push(new GitOpsTemplateNode(template)); + } + } + + return nodes; + } +} diff --git a/src/views/dataProviders/workloadDataProvider.ts b/src/views/dataProviders/workloadDataProvider.ts new file mode 100644 index 00000000..0ac72d73 --- /dev/null +++ b/src/views/dataProviders/workloadDataProvider.ts @@ -0,0 +1,188 @@ +import { ContextTypes, setVSCodeContext } from '../../vscodeContext'; +import { fluxTools } from '../../flux/fluxTools'; +import { FluxTreeResources } from '../../flux/fluxTypes'; +import { kubernetesTools } from '../../kubernetes/kubernetesTools'; +import { KubernetesObjectKinds, NamespaceResult } from '../../kubernetes/types/kubernetesTypes'; +import { statusBar } from '../../statusBar'; +import { AnyResourceNode } from '../nodes/anyResourceNode'; +import { HelmReleaseNode } from '../nodes/helmReleaseNode'; +import { KustomizationNode } from '../nodes/kustomizationNode'; +import { NamespaceNode } from '../nodes/namespaceNode'; +import { TreeNode } from '../nodes/treeNode'; +import { WorkloadNode } from '../nodes/workloadNode'; +import { refreshWorkloadsTreeView } from '../treeViews'; +import { DataProvider } from './dataProvider'; +import { sortByMetadataName } from '../../kubernetes/kubernetesUtils'; + +/** + * Defines data provider for loading Kustomizations + * and Helm Releases in Workloads Tree View. + */ +export class WorkloadDataProvider extends DataProvider { + + namespaceResult?: NamespaceResult; + + /** + * Creates Workload tree nodes for the currently selected kubernetes cluster. + * @returns Workload tree nodes to display. + */ + async buildTree(): Promise { + statusBar.startLoadingTree(); + + const workloadNodes: WorkloadNode[] = []; + + setVSCodeContext(ContextTypes.LoadingWorkloads, true); + + const [kustomizations, helmReleases, namespaces] = await Promise.all([ + // Fetch all workloads + kubernetesTools.getKustomizations(), + kubernetesTools.getHelmReleases(), + // Fetch namespaces to group the nodes + kubernetesTools.getNamespaces(), + // cache resource kinds + kubernetesTools.getAvailableResourceKinds(), + ]); + + this.namespaceResult = namespaces; + + if (kustomizations) { + for (const kustomizeWorkload of sortByMetadataName(kustomizations.items)) { + workloadNodes.push(new KustomizationNode(kustomizeWorkload)); + } + } + + if (helmReleases) { + for (const helmRelease of sortByMetadataName(helmReleases.items)) { + workloadNodes.push(new HelmReleaseNode(helmRelease)); + } + } + + for (const node of workloadNodes) { + this.updateWorkloadChildren(node); + } + + setVSCodeContext(ContextTypes.LoadingWorkloads, false); + setVSCodeContext(ContextTypes.NoWorkloads, workloadNodes.length === 0); + statusBar.stopLoadingTree(); + + + return this.groupByNamespace(namespaces?.items || [], workloadNodes); + } + + buildWorkloadsTree(node: TreeNode, resourceTree: FluxTreeResources[], parentNamespace = '') { + for (const resource of resourceTree) { + if (resource.resource.GroupKind.Kind === KubernetesObjectKinds.Namespace) { + continue; + } + + // Nested items have empty namespace https://github.com/fluxcd/flux2/issues/2149 + const namespace = resource.resource.Namespace || parentNamespace; + + const childNode = new AnyResourceNode({ + kind: resource.resource.GroupKind.Kind, + metadata: { + name: resource.resource.Name, + namespace, + }, + }); + + node.addChild(childNode); + + if (resource.resources && resource.resources.length) { + this.buildWorkloadsTree(childNode, resource.resources, namespace); + } + } + } + /** + * Fetch all kubernetes resources that were created by a kustomize/helmRelease + * and add them as child nodes of the workload. + * @param workloadNode target workload node + */ + async updateWorkloadChildren(workloadNode: WorkloadNode) { + const name = workloadNode.resource.metadata.name || ''; + const namespace = workloadNode.resource.metadata.namespace || ''; + const targetNamespace = workloadNode.resource.spec.targetNamespace || namespace; + + let workloadChildren; + if (workloadNode instanceof KustomizationNode) { + const resourceTree = await fluxTools.tree(name, namespace); + + if (!resourceTree || !resourceTree.resources) { + workloadNode.children = [new TreeNode('No Resources')]; + refreshWorkloadsTreeView(workloadNode); + return; + } + + this.buildWorkloadsTree(workloadNode, resourceTree.resources); + refreshWorkloadsTreeView(workloadNode); + + return; + } else if (workloadNode instanceof HelmReleaseNode) { + // TODO: use `flux tree` to fetch the resources + workloadChildren = await kubernetesTools.getChildrenOfWorkload('helm', name, targetNamespace); + } + + if (!workloadChildren) { + workloadNode.children = [new TreeNode('No Resources')]; + refreshWorkloadsTreeView(workloadNode); + return; + } + + // Get all namespaces + const namespaces = this.namespaceResult || await kubernetesTools.getNamespaces(); + if (!namespaces) { + return; + } + + const namespaceNodes = namespaces.items.map(ns => new NamespaceNode(ns)); + namespaceNodes.forEach(namespaceNode => namespaceNode.expand()); + + /* + * Do not delete empty namespace if it was in the fetched resources. + * Workloads can create namespace kubernetes resources. + */ + const exceptNamespaces: string[] = []; + + // group children of workload by namespace + for (const namespaceNode of namespaceNodes) { + for (const workloadChild of workloadChildren.items) { + if (workloadChild.kind !== KubernetesObjectKinds.Namespace && + workloadChild.metadata?.namespace === namespaceNode.resource.metadata.name) { + namespaceNode.addChild(new AnyResourceNode(workloadChild)); + } else { + const namespaceName = namespaceNode.resource.metadata.name; + if (namespaceName) { + exceptNamespaces.push(); + } + } + } + } + + // only show namespaces that are not empty + workloadNode.children = namespaceNodes.filter( + namespaceNode => !exceptNamespaces.some(exceptNamespace => exceptNamespace !== namespaceNode.resource.metadata.name) + && namespaceNode.children.length); + + if(workloadNode.children.length === 0) { + workloadNode.children = [new TreeNode('No Resources')]; + } + + refreshWorkloadsTreeView(workloadNode); + } + + /** + * This is called when the tree node is being expanded. + * @param workloadNode target node or undefined when at the root level. + */ + async getChildren(workloadNode?: KustomizationNode | HelmReleaseNode) { + if (workloadNode) { + if (workloadNode.children.length) { + return workloadNode.children; + } else { + return [new TreeNode('Loading...')]; + } + } else { + return await this.buildTree(); + } + } +} diff --git a/src/ui/treeviews/documentationConfig.ts b/src/views/documentationConfig.ts similarity index 100% rename from src/ui/treeviews/documentationConfig.ts rename to src/views/documentationConfig.ts diff --git a/src/views/nodes/anyResourceNode.ts b/src/views/nodes/anyResourceNode.ts new file mode 100644 index 00000000..778d4fff --- /dev/null +++ b/src/views/nodes/anyResourceNode.ts @@ -0,0 +1,26 @@ +import { KubernetesObject } from '@kubernetes/client-node'; +import { TreeNode } from './treeNode'; + +/** + * Defines any kubernetes resourse. + */ +export class AnyResourceNode extends TreeNode { + + /** + * kubernetes resource metadata + */ + resource: KubernetesObject; + + constructor(anyResource: KubernetesObject) { + super(anyResource.metadata?.name || ''); + + this.description = anyResource.kind; + + // save metadata reference + this.resource = anyResource; + } + + get tooltip() { + return ''; + } +} diff --git a/src/views/nodes/bucketNode.ts b/src/views/nodes/bucketNode.ts new file mode 100644 index 00000000..70a3bf36 --- /dev/null +++ b/src/views/nodes/bucketNode.ts @@ -0,0 +1,28 @@ +import { Bucket } from '../../kubernetes/types/flux/bucket'; +import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { SourceNode } from './sourceNode'; + +/** + * Defines Bucket tree view item for display in GitOps Sources tree view. + */ +export class BucketNode extends SourceNode { + + /** + * Bucket kubernetes resource object + */ + resource: Bucket; + + /** + * Creates new bucket tree view item for display. + * @param bucket Bucket kubernetes object info. + */ + constructor(bucket: Bucket) { + super(`${KubernetesObjectKinds.Bucket}: ${bucket.metadata?.name}`, bucket); + + this.resource = bucket; + } + + get contexts() { + return [KubernetesObjectKinds.Bucket]; + } +} diff --git a/src/views/nodes/clusterContextNode.ts b/src/views/nodes/clusterContextNode.ts new file mode 100644 index 00000000..a5cc2837 --- /dev/null +++ b/src/views/nodes/clusterContextNode.ts @@ -0,0 +1,158 @@ +import { ExtensionMode, MarkdownString } from 'vscode'; +import { CommandId } from '../../commands'; +import { globalState } from '../../extension'; +import { getExtensionContext } from '../../extensionContext'; +import { extensionState } from '../../extensionState'; +import { KubernetesCluster, KubernetesContextWithCluster } from '../../kubernetes/types/kubernetesConfig'; +import { kubernetesTools } from '../../kubernetes/kubernetesTools'; +import { ClusterProvider } from '../../kubernetes/types/kubernetesTypes'; +import { createMarkdownHr, createMarkdownTable } from '../../utils/markdownUtils'; +import { ContextTypes, setVSCodeContext } from '../../vscodeContext'; +import { NodeContext } from './nodeContext'; +import { TreeNode } from './treeNode'; + +/** + * Defines Cluster tree view item for displaying + * kubernetes contexts inside the Clusters tree view. + */ +export class ClusterContextNode extends TreeNode { + + /** + * Whether cluster is managed by AKS or Azure ARC + * or some other provider. + */ + private clusterProvider: ClusterProvider = ClusterProvider.Unknown; + + /** + * User used "Set Cluster Provider" context menu item + * to override the cluster provider detection. + */ + private clusterProviderManuallyOverridden = false; + + /** + * Cluster object. + */ + private cluster?: KubernetesCluster; + + /** + * Cluster context. + */ + private clusterContext: KubernetesContextWithCluster; + + /** + * Context name. + */ + contextName: string; + + /** + * Cluster name. + */ + clusterName: string; + + /** + * Current/active cluster/context. + */ + isCurrent = false; + + /** + * Whether or not gitops is installed on this cluster. + * `undefined` when it's not yet initialized or when detection failed. + */ + isGitOpsEnabled?: boolean; + + /** + * Creates new Cluster tree view item for display. + * @param kubernetesContext Cluster object info. + */ + constructor(kubernetesContext: KubernetesContextWithCluster) { + super(kubernetesContext.name); + + this.cluster = kubernetesContext.context.clusterInfo; + this.clusterContext = kubernetesContext; + this.clusterName = kubernetesContext.context.clusterInfo?.name || kubernetesContext.name; + this.contextName = kubernetesContext.name; + this.description = kubernetesContext.context.clusterInfo?.cluster.server; + + this.setIcon('cloud'); + } + + /** + * Set context/icon and refresh the node: + * - Whether or not GitOps is enabled + * - Cluster provider. + */ + async updateNodeContext() { + this.isGitOpsEnabled = await kubernetesTools.isGitOpsEnabled(this.contextName); + + const clusterMetadata = globalState.getClusterMetadata(this.clusterName); + if (clusterMetadata?.clusterProvider) { + this.clusterProviderManuallyOverridden = true; + } + this.clusterProvider = clusterMetadata?.clusterProvider || await kubernetesTools.detectClusterProvider(this.contextName); + + // Update vscode context for welcome view of other tree views + if (this.isCurrent && typeof this.isGitOpsEnabled === 'boolean') { + setVSCodeContext(ContextTypes.CurrentClusterGitOpsNotEnabled, !this.isGitOpsEnabled); + } + + if (this.isGitOpsEnabled) { + this.setIcon('cloud-gitops'); + } else { + this.setIcon('cloud'); + } + } + + get tooltip(): MarkdownString { + return this.getMarkdownHover(this.clusterContext); + } + + /** + * Creates markdwon string for the Cluster tree view item tooltip. + * @param cluster Cluster info object. + */ + getMarkdownHover(cluster: KubernetesContextWithCluster): MarkdownString { + const markdown: MarkdownString = createMarkdownTable(cluster); + + createMarkdownHr(markdown); + markdown.appendMarkdown(`Flux Version: ${extensionState.get('fluxVersion')}`); + + if (this.clusterProvider !== ClusterProvider.Generic || this.clusterProviderManuallyOverridden) { + createMarkdownHr(markdown); + markdown.appendMarkdown(`Cluster Provider: ${this.clusterProvider}`); + if (this.clusterProviderManuallyOverridden) { + markdown.appendMarkdown(' (User override)'); + } + } + + return markdown; + } + + // @ts-ignore + get command() { + // Allow click to swith current kubernetes context only when developing extension + if (getExtensionContext().extensionMode === ExtensionMode.Development) { + return { + command: CommandId.SetCurrentKubernetesContext, + arguments: [this], + title: 'Set Context', + }; + } + } + + get contexts() { + const result = [NodeContext.Cluster]; + + if (typeof this.isGitOpsEnabled === 'boolean') { + result.push( + this.isGitOpsEnabled ? NodeContext.ClusterGitOpsEnabled : NodeContext.ClusterGitOpsNotEnabled, + ); + } + + result.push( + this.isCurrent ? NodeContext.CurrentCluster : NodeContext.NotCurrentCluster, + ); + + return result; + } + +} diff --git a/src/ui/treeviews/nodes/cluster/clusterDeploymentNode.ts b/src/views/nodes/clusterDeploymentNode.ts similarity index 68% rename from src/ui/treeviews/nodes/cluster/clusterDeploymentNode.ts rename to src/views/nodes/clusterDeploymentNode.ts index d5702bda..ff4bc0eb 100644 --- a/src/ui/treeviews/nodes/cluster/clusterDeploymentNode.ts +++ b/src/views/nodes/clusterDeploymentNode.ts @@ -1,11 +1,10 @@ -import { Deployment } from 'types/kubernetes/kubernetesTypes'; -import { CommonIcon } from 'ui/icons'; -import { ClusterTreeNode } from './clusterTreeNode'; +import { Deployment, KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { TreeNode, TreeNodeIcon } from './treeNode'; /** * Defines deployment tree view item for display in GitOps Clusters tree view. */ -export class ClusterDeploymentNode extends ClusterTreeNode { +export class ClusterDeploymentNode extends TreeNode { /** * Cluster deployment kubernetes resource object @@ -13,13 +12,13 @@ export class ClusterDeploymentNode extends ClusterTreeNode { resource: Deployment; constructor(deployment: Deployment) { - super(deployment.metadata.name); + super(deployment.metadata.name || ''); this.resource = deployment; this.label = this.getImageName(deployment); - this.setCommonIcon(CommonIcon.Unknown); + this.setIcon(TreeNodeIcon.Unknown); } /** @@ -38,10 +37,14 @@ export class ClusterDeploymentNode extends ClusterTreeNode { */ setStatus(status: 'success' | 'failure') { if (status === 'success') { - this.setCommonIcon(CommonIcon.Success); + this.setIcon(TreeNodeIcon.Success); } else if (status === 'failure') { - this.setCommonIcon(CommonIcon.Warning); + this.setIcon(TreeNodeIcon.Warning); } } + + get contexts() { + return [KubernetesObjectKinds.Deployment]; + } } diff --git a/src/ui/treeviews/nodes/documentationNode.ts b/src/views/nodes/documentationNode.ts similarity index 81% rename from src/ui/treeviews/nodes/documentationNode.ts rename to src/views/nodes/documentationNode.ts index ff499d0d..a665ee7a 100644 --- a/src/ui/treeviews/nodes/documentationNode.ts +++ b/src/views/nodes/documentationNode.ts @@ -1,9 +1,7 @@ import { Uri } from 'vscode'; - -import { CommandId } from 'types/extensionIds'; -import { asAbsolutePath } from 'utils/asAbsolutePath'; +import { CommandId } from '../../commands'; +import { asAbsolutePath } from '../../extensionContext'; import { DocumentationLink } from '../documentationConfig'; -import { documentationDataProvider } from '../treeViews'; import { TreeNode } from './treeNode'; /** @@ -19,7 +17,7 @@ export class DocumentationNode extends TreeNode { newUserGuide?: boolean; constructor(link: DocumentationLink, isParent = false) { - super(link.title, documentationDataProvider); + super(link.title); this.title = link.title; this.newUserGuide = link.newUserGuide; diff --git a/src/views/nodes/gitOpsTemplateNode.ts b/src/views/nodes/gitOpsTemplateNode.ts new file mode 100644 index 00000000..d2e0fad2 --- /dev/null +++ b/src/views/nodes/gitOpsTemplateNode.ts @@ -0,0 +1,39 @@ +import { MarkdownString, ThemeColor, ThemeIcon } from 'vscode'; +import { GitOpsTemplate } from '../../kubernetes/types/flux/gitOpsTemplate'; +import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { createMarkdownTable } from '../../utils/markdownUtils'; +import { TreeNode } from './treeNode'; + +/** + * Base class for all the Source tree view items. + */ +export class GitOpsTemplateNode extends TreeNode { + resource: GitOpsTemplate; + + constructor(template: GitOpsTemplate) { + super(template.metadata.name || 'No name'); + + this.resource = template; + + this.setIcon(new ThemeIcon('notebook-render-output', new ThemeColor('editorWidget.foreground'))); + } + + get tooltip() { + return this.getMarkdownHover(this.resource); + } + + // @ts-ignore + get description() { + // return 'Description'; + return false; + } + + getMarkdownHover(template: GitOpsTemplate): MarkdownString { + const markdown: MarkdownString = createMarkdownTable(template); + return markdown; + } + + get contexts() { + return [KubernetesObjectKinds.GitOpsTemplate]; + } +} diff --git a/src/views/nodes/gitRepositoryNode.ts b/src/views/nodes/gitRepositoryNode.ts new file mode 100644 index 00000000..d9cb1bd2 --- /dev/null +++ b/src/views/nodes/gitRepositoryNode.ts @@ -0,0 +1,33 @@ +import { GitRepository } from '../../kubernetes/types/flux/gitRepository'; +import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { NodeContext } from './nodeContext'; +import { SourceNode } from './sourceNode'; + +/** + * Defines GitRepository tree view item for display in GitOps Sources tree view. + */ +export class GitRepositoryNode extends SourceNode { + + /** + * Git repository kubernetes resource object + */ + resource: GitRepository; + + /** + * Creates new git repository tree view item for display. + * @param gitRepository Git repository kubernetes object info. + */ + constructor(gitRepository: GitRepository) { + super(`${KubernetesObjectKinds.GitRepository}: ${gitRepository.metadata?.name}`, gitRepository); + + this.resource = gitRepository; + } + + get contexts() { + const contextsArr: string[] = [KubernetesObjectKinds.GitRepository]; + contextsArr.push( + this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, + ); + return contextsArr; + } +} diff --git a/src/views/nodes/helmReleaseNode.ts b/src/views/nodes/helmReleaseNode.ts new file mode 100644 index 00000000..8e7282e3 --- /dev/null +++ b/src/views/nodes/helmReleaseNode.ts @@ -0,0 +1,36 @@ +import { HelmRelease } from '../../kubernetes/types/flux/helmRelease'; +import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { NodeContext } from './nodeContext'; +import { WorkloadNode } from './workloadNode'; + +/** + * Defines Helm release tree view item for display in GitOps Workloads tree view. + */ +export class HelmReleaseNode extends WorkloadNode { + + /** + * Helm release kubernetes resource object + */ + resource: HelmRelease; + + /** + * Creates new helm release tree view item for display. + * @param helmRelease Helm release kubernetes object info. + */ + constructor(helmRelease: HelmRelease) { + super(helmRelease.metadata?.name || '', helmRelease); + + this.resource = helmRelease; + + this.makeCollapsible(); + + } + + get contexts() { + const contextsArr: string[] = [KubernetesObjectKinds.HelmRelease]; + contextsArr.push( + this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, + ); + return contextsArr; + } +} diff --git a/src/views/nodes/helmRepositoryNode.ts b/src/views/nodes/helmRepositoryNode.ts new file mode 100644 index 00000000..cc9f42ce --- /dev/null +++ b/src/views/nodes/helmRepositoryNode.ts @@ -0,0 +1,33 @@ +import { HelmRepository } from '../../kubernetes/types/flux/helmRepository'; +import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { NodeContext } from './nodeContext'; +import { SourceNode } from './sourceNode'; + +/** + * Defines HelmRepository tree view item for display in GitOps Sources tree view. + */ +export class HelmRepositoryNode extends SourceNode { + + /** + * Helm repository kubernetes resource object + */ + resource: HelmRepository; + + /** + * Creates new helm repository tree view item for display. + * @param helmRepository Helm repository kubernetes object info. + */ + constructor(helmRepository: HelmRepository) { + super(`${KubernetesObjectKinds.HelmRepository}: ${helmRepository.metadata?.name}`, helmRepository); + + this.resource = helmRepository; + } + + get contexts() { + const contextsArr: string[] = [KubernetesObjectKinds.HelmRepository]; + contextsArr.push( + this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, + ); + return contextsArr; + } +} diff --git a/src/views/nodes/kustomizationNode.ts b/src/views/nodes/kustomizationNode.ts new file mode 100644 index 00000000..b336202d --- /dev/null +++ b/src/views/nodes/kustomizationNode.ts @@ -0,0 +1,34 @@ +import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { Kustomize } from '../../kubernetes/types/flux/kustomize'; +import { NodeContext } from './nodeContext'; +import { WorkloadNode } from './workloadNode'; + +/** + * Defines Kustomization tree view item for display in GitOps Workload tree view. + */ +export class KustomizationNode extends WorkloadNode { + /** + * Kustomize kubernetes resource object + */ + resource: Kustomize; + + /** + * Creates new app kustomization tree view item for display. + * @param kustomization Kustomize kubernetes object info. + */ + constructor(kustomization: Kustomize) { + super(kustomization.metadata?.name || '', kustomization); + + this.resource = kustomization; + + this.makeCollapsible(); + } + + get contexts() { + const contextsArr: string[] = [KubernetesObjectKinds.Kustomization]; + contextsArr.push( + this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, + ); + return contextsArr; + } +} diff --git a/src/views/nodes/namespaceNode.ts b/src/views/nodes/namespaceNode.ts new file mode 100644 index 00000000..febfe0eb --- /dev/null +++ b/src/views/nodes/namespaceNode.ts @@ -0,0 +1,25 @@ +import { KubernetesObjectKinds, Namespace } from '../../kubernetes/types/kubernetesTypes'; +import { TreeNode } from './treeNode'; + +/** + * Defines any kubernetes resourse. + */ +export class NamespaceNode extends TreeNode { + + /** + * kubernetes resource metadata + */ + resource: Namespace; + + constructor(namespace: Namespace) { + super(namespace.metadata?.name || ''); + + this.description = KubernetesObjectKinds.Namespace; + + this.resource = namespace; + } + + get contexts() { + return [KubernetesObjectKinds.Namespace]; + } +} diff --git a/src/types/nodeContext.ts b/src/views/nodes/nodeContext.ts similarity index 81% rename from src/types/nodeContext.ts rename to src/views/nodes/nodeContext.ts index 9ed953db..916c9dba 100644 --- a/src/types/nodeContext.ts +++ b/src/views/nodes/nodeContext.ts @@ -2,6 +2,7 @@ * Defines GitOps tree view node context values. */ export const enum NodeContext { + // Cluster context values Cluster = 'cluster', CurrentCluster = 'currentCluster', @@ -11,15 +12,11 @@ export const enum NodeContext { ClusterGitOpsEnabled = 'clusterGitOpsEnabled', ClusterGitOpsNotEnabled = 'clusterGitOpsNotEnabled', + // resource contexts + AzureFluxConfig = 'azureFluxConfig', + NotAzureFluxConfig = 'NotAzureFluxConfig', // Generic context values Suspend = 'suspend', NotSuspend = 'notSuspend', - - // WGE - HasWgePortal = 'hasWgePortal', - - // Pipeline - ManualPromotion = 'manualPromotion', - AutoPromotion = 'autoPromotion', } diff --git a/src/views/nodes/ociRepositoryNode.ts b/src/views/nodes/ociRepositoryNode.ts new file mode 100644 index 00000000..17f0ab52 --- /dev/null +++ b/src/views/nodes/ociRepositoryNode.ts @@ -0,0 +1,33 @@ +import { OCIRepository } from '../../kubernetes/types/flux/ociRepository'; +import { KubernetesObjectKinds } from '../../kubernetes/types/kubernetesTypes'; +import { NodeContext } from './nodeContext'; +import { SourceNode } from './sourceNode'; + +/** + * Defines OCIRepository tree view item for display in GitOps Sources tree view. + */ +export class OCIRepositoryNode extends SourceNode { + + /** + * OCI repository kubernetes resource object + */ + resource: OCIRepository; + + /** + * Creates new oci repository tree view item for display. + * @param ociRepository OCI repository kubernetes object info. + */ + constructor(ociRepository: OCIRepository) { + super(`${KubernetesObjectKinds.OCIRepository}: ${ociRepository.metadata?.name}`, ociRepository); + + this.resource = ociRepository; + } + + get contexts() { + const contextsArr: string[] = [KubernetesObjectKinds.OCIRepository]; + contextsArr.push( + this.resource.spec.suspend ? NodeContext.Suspend : NodeContext.NotSuspend, + ); + return contextsArr; + } +} diff --git a/src/views/nodes/sourceNode.ts b/src/views/nodes/sourceNode.ts new file mode 100644 index 00000000..c4b79dc3 --- /dev/null +++ b/src/views/nodes/sourceNode.ts @@ -0,0 +1,92 @@ +import { MarkdownString } from 'vscode'; +import { Bucket } from '../../kubernetes/types/flux/bucket'; +import { GitRepository } from '../../kubernetes/types/flux/gitRepository'; +import { OCIRepository } from '../../kubernetes/types/flux/ociRepository'; +import { HelmRepository } from '../../kubernetes/types/flux/helmRepository'; +import { DeploymentCondition } from '../../kubernetes/types/kubernetesTypes'; +import { createMarkdownError, createMarkdownHr, createMarkdownTable } from '../../utils/markdownUtils'; +import { shortenRevision } from '../../utils/stringUtils'; +import { TreeNode, TreeNodeIcon } from './treeNode'; + +/** + * Base class for all the Source tree view items. + */ +export class SourceNode extends TreeNode { + + resource: GitRepository | OCIRepository | HelmRepository | Bucket; + + /** + * Whether or not the source failed to reconcile. + */ + isReconcileFailed = false; + + constructor(label: string, source: GitRepository | OCIRepository | HelmRepository | Bucket) { + super(label); + + this.resource = source; + + // update reconciliation status + this.updateStatus(source); + } + + get tooltip() { + return this.getMarkdownHover(this.resource); + } + + // @ts-ignore + get description() { + const isSuspendIcon = this.resource.spec?.suspend ? '⏸ ' : ''; + let revisionOrError = ''; + + if (this.isReconcileFailed) { + revisionOrError = `${this.findReadyOrFirstCondition(this.resource.status.conditions)?.reason}`; + } else { + revisionOrError = shortenRevision(this.resource.status.artifact?.revision); + } + + return `${isSuspendIcon}${revisionOrError}`; + } + + /** + * Creates markdwon string for Source tree view item tooltip. + * @param source GitRepository, HelmRepository or Bucket kubernetes object. + * @returns Markdown string to use for Source tree view item tooltip. + */ + getMarkdownHover(source: GitRepository | OCIRepository | HelmRepository | Bucket): MarkdownString { + const markdown: MarkdownString = createMarkdownTable(source); + + // show status in hover when source fetching failed + if (this.isReconcileFailed) { + const readyCondition = this.findReadyOrFirstCondition(source.status.conditions); + createMarkdownHr(markdown); + createMarkdownError('Status message', readyCondition?.message, markdown); + createMarkdownError('Status reason', readyCondition?.reason, markdown); + } + + return markdown; + } + + /** + * Find condition with the "Ready" type or + * return first one if "Ready" not found. + * + * @param conditions "status.conditions" of the source + */ + findReadyOrFirstCondition(conditions?: DeploymentCondition[]): DeploymentCondition | undefined { + return conditions?.find(condition => condition.type === 'Ready') || conditions?.[0]; + } + + /** + * Update source status with showing error icon when fetch failed. + * @param source target source + */ + updateStatus(source: GitRepository | OCIRepository | HelmRepository | Bucket): void { + if (this.findReadyOrFirstCondition(source.status.conditions)?.status === 'True') { + this.setIcon(TreeNodeIcon.Success); + this.isReconcileFailed = false; + } else { + this.setIcon(TreeNodeIcon.Error); + this.isReconcileFailed = true; + } + } +} diff --git a/src/ui/treeviews/nodes/treeNode.ts b/src/views/nodes/treeNode.ts similarity index 50% rename from src/ui/treeviews/nodes/treeNode.ts rename to src/views/nodes/treeNode.ts index 8d4375df..1e74646b 100644 --- a/src/ui/treeviews/nodes/treeNode.ts +++ b/src/views/nodes/treeNode.ts @@ -1,15 +1,29 @@ -import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; - -import { CommonIcon, commonIcon } from 'ui/icons'; -import { asAbsolutePath } from 'utils/asAbsolutePath'; -import { InfoLabel, infoNode } from 'utils/makeTreeviewInfoNode'; -import { SimpleDataProvider } from '../dataProviders/simpleDataProvider'; +import { KubernetesObject } from '@kubernetes/client-node'; +import { Command, MarkdownString, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; +import { CommandId } from '../../commands'; +import { asAbsolutePath } from '../../extensionContext'; +import { FileTypes } from '../../fileTypes'; +import { KubernetesContextWithCluster } from '../../kubernetes/types/kubernetesConfig'; +import { kubernetesTools } from '../../kubernetes/kubernetesTools'; +import { createMarkdownTable, KnownTreeNodeResources } from '../../utils/markdownUtils'; +import { GitOpsTemplate } from '../../kubernetes/types/flux/gitOpsTemplate'; + +export const enum TreeNodeIcon { + Error = 'error', + Warning = 'warning', + Success = 'success', + Unknown = 'unknown', +} /** * Defines tree view item base class used by all GitOps tree views. */ export class TreeNode extends TreeItem { - resource?: any; + + /** + * Kubernetes resource. + */ + resource?: Exclude | GitOpsTemplate | KubernetesObject; /** * Reference to the parent node (if exists). @@ -19,33 +33,14 @@ export class TreeNode extends TreeItem { /** * Reference to all the child nodes. */ - private _children: TreeNode[] = []; - - get children(): TreeNode[] { - return this._children; - } - - set children(cs: TreeNode[]) { - this._children = cs; - this._children.forEach(c => c.parent = this); - } - - dataProvider: SimpleDataProvider; - - /* - * async load children for the node - */ - async updateChildren() { - // no-op - } + children: TreeNode[] = []; /** * Creates new tree node. * @param label Tree node label */ - constructor(label: string, dataProvider: SimpleDataProvider) { + constructor(label: string) { super(label, TreeItemCollapsibleState.None); - this.dataProvider = dataProvider; } /** @@ -55,11 +50,6 @@ export class TreeNode extends TreeItem { this.collapsibleState = TreeItemCollapsibleState.Collapsed; } - makeUncollapsible() { - this.collapsibleState = TreeItemCollapsibleState.None; - } - - /** * Expands a tree node and shows its children. */ @@ -67,17 +57,6 @@ export class TreeNode extends TreeItem { this.collapsibleState = TreeItemCollapsibleState.Expanded; } - /** - * Update icon and other status properties. - */ - updateStatus(): void {} - - redraw() { - if(this.dataProvider) { - this.dataProvider.redraw(this); - } - } - /** * Sets tree view item icon. * @@ -85,8 +64,16 @@ export class TreeNode extends TreeItem { * relative file path `resouces/icons/(dark|light)/${icon}.svg` * @param icon Theme icon, uri or light/dark svg icon path. */ - setIcon(icon: string | ThemeIcon | Uri |undefined) { - if (typeof icon === 'string') { + setIcon(icon: string | ThemeIcon | Uri | TreeNodeIcon) { + if (icon === TreeNodeIcon.Error) { + this.iconPath = new ThemeIcon('error', new ThemeColor('editorError.foreground')); + } else if (icon === TreeNodeIcon.Warning) { + this.iconPath = new ThemeIcon('warning', new ThemeColor('editorWarning.foreground')); + } else if (icon === TreeNodeIcon.Success) { + this.iconPath = new ThemeIcon('pass', new ThemeColor('terminal.ansiGreen')); + } else if (icon === TreeNodeIcon.Unknown) { + this.iconPath = new ThemeIcon('circle-large-outline'); + } else if (typeof icon === 'string') { this.iconPath = { light: asAbsolutePath(`resources/icons/light/${icon}.svg`), dark: asAbsolutePath(`resources/icons/dark/${icon}.svg`), @@ -96,11 +83,6 @@ export class TreeNode extends TreeItem { } } - setCommonIcon(icon: CommonIcon) { - this.iconPath = commonIcon(icon); - } - - /** * Add new tree view item to the children collection. * @param child Child tree view item to add. @@ -118,11 +100,6 @@ export class TreeNode extends TreeItem { return this; } - removeChild(child: TreeNode) { - this.children = this.children.filter(c => c !== child); - } - - /** * * VSCode doesn't support multiple contexts on the Tree Nodes, only string. @@ -141,57 +118,43 @@ export class TreeNode extends TreeItem { .join(''); } + // @ts-ignore + get tooltip(): string | MarkdownString { + if (this.resource) { + return createMarkdownTable(this.resource as KnownTreeNodeResources); + } + } + + // @ts-ignore + get command(): Command | undefined { + // Set click event handler to load kubernetes resource as yaml file in editor. + if (this.resource) { + const resourceUri = kubernetesTools.getResourceUri( + this.resource.metadata?.namespace, + `${this.resource.kind}/${this.resource.metadata?.name}`, + FileTypes.Yaml, + ); + + return { + command: CommandId.EditorOpenResource, + arguments: [resourceUri], + title: 'View Resource', + }; + } + } + /** * VSCode contexts to use for setting {@link contextValue} * of this tree node. Used for context/inline menus. - * - * Contexts are used to enable/disable menu items. */ get contexts(): string[] { return []; } - /** - * - * Context for types of resources. - */ - get contextType(): string | undefined { - return; - } - // @ts-ignore get contextValue() { - const cs = [...this.contexts]; - if(this.contextType) { - cs.push(this.contextType); - } - if(cs.length) { - return this.joinContexts(cs); + if (this.contexts.length) { + return this.joinContexts(this.contexts); } } - - // @ts-ignore - get tooltip(): string | MarkdownString { - return ''; - } - - // @ts-ignore - get command(): Command | undefined {} - - - get viewStateKey(): string { - return ''; - } - - - infoNodes(type: InfoLabel) { - return [this.infoNode(type)]; - } - - infoNode(type: InfoLabel) { - return infoNode(type, this.dataProvider); - } } - - - diff --git a/src/views/nodes/workloadNode.ts b/src/views/nodes/workloadNode.ts new file mode 100644 index 00000000..d465e11e --- /dev/null +++ b/src/views/nodes/workloadNode.ts @@ -0,0 +1,97 @@ +import { MarkdownString } from 'vscode'; +import { HelmRelease } from '../../kubernetes/types/flux/helmRelease'; +import { DeploymentCondition } from '../../kubernetes/types/kubernetesTypes'; +import { Kustomize } from '../../kubernetes/types/flux/kustomize'; +import { createMarkdownError, createMarkdownHr, createMarkdownTable } from '../../utils/markdownUtils'; +import { TreeNode, TreeNodeIcon } from './treeNode'; +import { shortenRevision } from '../../utils/stringUtils'; + +/** + * Base class for all Workload tree view items. + */ +export class WorkloadNode extends TreeNode { + + /** + * Whether or not the application failed to reconcile. + */ + isReconcileFailed = false; + + resource: Kustomize | HelmRelease; + + constructor(label: string, resource: Kustomize | HelmRelease) { + + super(`${resource.kind}: ${label}`); + + this.resource = resource; + + this.updateStatus(resource); + } + + /** + * Find condition with the "Ready" type or + * return first one if "Ready" not found. + * + * @param conditions "status.conditions" of the workload + */ + findReadyOrFirstCondition(conditions?: DeploymentCondition | DeploymentCondition[]): DeploymentCondition | undefined { + if (Array.isArray(conditions)) { + return conditions.find(condition => condition.type === 'Ready') || conditions[0]; + } else { + return conditions; + } + } + + /** + * Update workload status with showing error icon when reconcile has failed. + * @param workload target resource + */ + updateStatus(workload: Kustomize | HelmRelease): void { + const condition = this.findReadyOrFirstCondition(workload.status.conditions); + + if (condition?.status === 'True') { + this.isReconcileFailed = false; + this.setIcon(TreeNodeIcon.Success); + } else { + this.isReconcileFailed = true; + this.setIcon(TreeNodeIcon.Error); + } + } + + get tooltip() { + const md = this.getMarkdownHover(this.resource); + return md; + + } + + // @ts-ignore + get description() { + const isSuspendIcon = this.resource.spec?.suspend ? '⏸ ' : ''; + let revisionOrError = ''; + + if (this.isReconcileFailed) { + revisionOrError = `${this.findReadyOrFirstCondition(this.resource.status.conditions)?.reason}`; + } else { + revisionOrError = shortenRevision(this.resource.status.lastAppliedRevision); + } + return `${isSuspendIcon}${revisionOrError}`; + } + + /** + * Creates markdwon string for Source tree view item tooltip. + * @param workload Kustomize or HelmRelease kubernetes object. + * @returns Markdown string to use for Source tree view item tooltip. + */ + getMarkdownHover(workload: Kustomize | HelmRelease): MarkdownString { + const markdown: MarkdownString = createMarkdownTable(workload); + + // show status in hover when source fetching failed + if (this.isReconcileFailed) { + const readyCondition = this.findReadyOrFirstCondition(workload.status.conditions); + createMarkdownHr(markdown); + createMarkdownError('Status message', readyCondition?.message, markdown); + createMarkdownError('Status reason', readyCondition?.reason, markdown); + } + + return markdown; + } +} diff --git a/src/views/treeViews.ts b/src/views/treeViews.ts new file mode 100644 index 00000000..67ee36de --- /dev/null +++ b/src/views/treeViews.ts @@ -0,0 +1,199 @@ +import { TreeItem, TreeView, window } from 'vscode'; +import { isAzureProvider } from '../azure/azureTools'; +import { Errorable, failed } from '../errorable'; +import { globalState } from '../extension'; +import { kubernetesTools } from '../kubernetes/kubernetesTools'; +import { ClusterInfo, ClusterProvider } from '../kubernetes/types/kubernetesTypes'; +import { ClusterDataProvider } from './dataProviders/clusterDataProvider'; +import { DocumentationDataProvider } from './dataProviders/documentationDataProvider'; +import { SourceDataProvider } from './dataProviders/sourceDataProvider'; +import { WorkloadDataProvider } from './dataProviders/workloadDataProvider'; +import { ClusterContextNode } from './nodes/clusterContextNode'; +import { TreeNode } from './nodes/treeNode'; +import { Views } from './views'; + +import * as k8s from 'vscode-kubernetes-tools-api'; +import { TemplateDataProvider } from './dataProviders/templateDataProvider'; + +export let clusterTreeViewProvider: ClusterDataProvider; +export let sourceTreeViewProvider: SourceDataProvider; +export let workloadTreeViewProvider: WorkloadDataProvider; +export let documentationTreeViewProvider: DocumentationDataProvider; +export let templateTreeViewProvider: TemplateDataProvider; + +let clusterTreeView: TreeView; +let sourceTreeView: TreeView; +let workloadTreeView: TreeView; +let documentationTreeView: TreeView; +let templateTreeView: TreeView; + +/** + * Creates tree views for the GitOps sidebar. + */ +export function createTreeViews() { + // create gitops tree view data providers + clusterTreeViewProvider = new ClusterDataProvider(); + sourceTreeViewProvider = new SourceDataProvider(); + workloadTreeViewProvider = new WorkloadDataProvider(); + documentationTreeViewProvider = new DocumentationDataProvider(); + templateTreeViewProvider = new TemplateDataProvider(); + + // create gitops sidebar tree views + clusterTreeView = window.createTreeView(Views.ClustersView, { + treeDataProvider: clusterTreeViewProvider, + showCollapseAll: true, + }); + + sourceTreeView = window.createTreeView(Views.SourcesView, { + treeDataProvider: sourceTreeViewProvider, + showCollapseAll: true, + }); + + workloadTreeView = window.createTreeView(Views.WorkloadsView, { + treeDataProvider: workloadTreeViewProvider, + showCollapseAll: true, + }); + + + // WGE templates + templateTreeView = window.createTreeView(Views.TemplatesView, { + treeDataProvider: templateTreeViewProvider, + showCollapseAll: true, + }); + + // create documentation links sidebar tree view + documentationTreeView = window.createTreeView(Views.DocumentationView, { + treeDataProvider: documentationTreeViewProvider, + showCollapseAll: true, + }); + + refreshWhenK8sContextChange(); + detectK8sConfigPathChange(); +} + +async function refreshWhenK8sContextChange() { + const configuration = await k8s.extension.configuration.v1_1; + if (!configuration.available) { + return; + } + configuration.api.onDidChangeContext(_context => { + refreshAllTreeViews(); + }); +} +async function detectK8sConfigPathChange() { + const configuration = await k8s.extension.configuration.v1_1; + if (!configuration.available) { + return; + } + configuration.api.onDidChangeKubeconfigPath(_path => { + refreshAllTreeViews(); + }); +} + +/** + * Refreshes all GitOps tree views. + */ +export function refreshAllTreeViews() { + refreshClustersTreeView(); + refreshResourcesTreeViews(); +} + +export function refreshResourcesTreeViews() { + refreshSourcesTreeView(); + refreshWorkloadsTreeView(); + refreshTemplatesTreeView(); +} + + +/** + * Reloads configured clusters tree view via kubectl. + * When an argument is passed - only that tree item + * and its children are updated. + */ +export function refreshClustersTreeView(node?: TreeNode) { + if (node && !clusterTreeViewProvider.includesTreeNode(node)) { + // Trying to refresh old (non-existent) cluster context node + return; + } + clusterTreeViewProvider.refresh(node); +} + +/** + * Reloads sources tree view for the selected cluster. + */ +export function refreshSourcesTreeView(node?: TreeNode) { + sourceTreeViewProvider.refresh(node); +} + +/** + * Reloads workloads tree view for the selected cluster. + */ +export function refreshWorkloadsTreeView(node?: TreeNode) { + workloadTreeViewProvider.refresh(node); +} + +/** + * Reloads workloads tree view for the selected cluster. + */ +export function refreshTemplatesTreeView(node?: TreeNode) { + templateTreeViewProvider.refresh(node); +} + +/** + * Get info about current cluster/context: + * 1. Cluster name + * 2. Context name + * 3. Detect cluster provider. + */ +export async function getCurrentClusterInfo(): Promise> { + const currentContextResult = await kubernetesTools.getCurrentContext(); + + if (failed(currentContextResult)) { + const error = `Failed to get current context ${currentContextResult.error[0]}`; + window.showErrorMessage(error); + return { + succeeded: false, + error: [error], + }; + } + const currentContextName = currentContextResult.result; + + + let currentClusterName = await kubernetesTools.getClusterName(currentContextName); + + // Pick user cluster provider override if defined + const clusterMetadata = globalState.getClusterMetadata(currentClusterName); + const isClusterProviderUserOverride = Boolean(clusterMetadata?.clusterProvider); + const currentClusterProvider = clusterMetadata?.clusterProvider || await kubernetesTools.detectClusterProvider(currentContextName); + + return { + succeeded: true, + result: { + clusterName: currentClusterName, + contextName: currentContextName, + clusterProvider: currentClusterProvider, + isClusterProviderUserOverride, + isAzure: isAzureProvider(currentClusterProvider), + }, + }; +} + +/** + * Expand, focus or select a tree node inside the Clusters tree view. + * @param clusterNode Target cluster node + */ +export async function revealClusterNode(clusterNode: ClusterContextNode, { + expand = false, + focus = false, + select = false, +}: { + expand?: boolean; + focus?: boolean; + select?: boolean; +} | undefined = {}) { + return await clusterTreeView.reveal(clusterNode, { + expand, + focus, + select, + }); +} diff --git a/src/views/views.ts b/src/views/views.ts new file mode 100644 index 00000000..f211c648 --- /dev/null +++ b/src/views/views.ts @@ -0,0 +1,10 @@ +/** + * GitOps view ids. + */ +export const enum Views { + ClustersView = 'gitops.views.clusters', + SourcesView = 'gitops.views.sources', + WorkloadsView = 'gitops.views.workloads', + TemplatesView = 'gitops.views.templates', + DocumentationView = 'gitops.views.documentation', +} diff --git a/src/vscodeContext.ts b/src/vscodeContext.ts new file mode 100644 index 00000000..4cdbff56 --- /dev/null +++ b/src/vscodeContext.ts @@ -0,0 +1,31 @@ +import { commands } from 'vscode'; +import { CommandId } from './commands'; + +/** + * GitOps context types. + */ +export const enum ContextTypes { + NoClusterSelected = 'gitops:noClusterSelected', + CurrentClusterGitOpsNotEnabled = 'gitops:currentClusterGitOpsNotEnabled', + + LoadingClusters = 'gitops:loadingClusters', + LoadingSources = 'gitops:loadingSources', + LoadingWorkloads = 'gitops:loadingWorkloads', + + FailedToLoadClusterContexts = 'gitops:failedToLoadClusterContexts', + NoClusters = 'gitops:noClusters', + NoSources = 'gitops:noSources', + NoWorkloads = 'gitops:noWorkloads', + + IsDev = 'gitops:isDev', + IsWGE = 'gitops:isWGE', +} + + +/** + * Type-safe way to set context for future use in: + * menus, keybindings, welcomeView... + */ +export async function setVSCodeContext(context: ContextTypes, value: boolean) { + return await commands.executeCommand(CommandId.VSCodeSetContext, context, value); +} diff --git a/src/ui/webviews/README.md b/src/webview-backend/README.md similarity index 100% rename from src/ui/webviews/README.md rename to src/webview-backend/README.md diff --git a/src/ui/webviews/WebviewBackend.ts b/src/webview-backend/WebviewBackend.ts similarity index 95% rename from src/ui/webviews/WebviewBackend.ts rename to src/webview-backend/WebviewBackend.ts index 048de746..a8f0964c 100644 --- a/src/ui/webviews/WebviewBackend.ts +++ b/src/webview-backend/WebviewBackend.ts @@ -1,9 +1,9 @@ import { Disposable, Uri, ViewColumn, Webview, WebviewPanel, window } from 'vscode'; import { camelCase } from 'change-case'; -import { asAbsolutePath } from 'utils/asAbsolutePath'; -import { getUri } from 'utils/getUri'; -import { WebviewParams } from 'types/webviewParams'; +import { asAbsolutePath } from '../extensionContext'; +import { getUri } from '../utils/getUri'; +import { WebviewParams } from './types'; export type MessageReceiver = (message: any, panel: WebviewPanel)=> any; diff --git a/src/ui/webviews/configureGitOps/actions.ts b/src/webview-backend/configureGitOps/actions.ts similarity index 87% rename from src/ui/webviews/configureGitOps/actions.ts rename to src/webview-backend/configureGitOps/actions.ts index 0696e0fd..9745f6b1 100644 --- a/src/ui/webviews/configureGitOps/actions.ts +++ b/src/webview-backend/configureGitOps/actions.ts @@ -1,10 +1,10 @@ -import { ParamsDictionary } from 'utils/typeUtils'; +import { ParamsDictionary } from '../../utils/typeUtils'; import { createConfigurationAzure } from './lib/createAzure'; import { createConfigurationGeneric } from './lib/createGeneric'; import { exportConfigurationGeneric } from './lib/exportGeneric'; -const isAzure = (data: ParamsDictionary) => data.clusterInfo.isAzure && (data.source?.createFluxConfig || !data.source); +const isAzure = (data: ParamsDictionary) => data.clusterInfo.isAzure && data.source?.createFluxConfig; function removeAzureData(data: any) { if(data.source) { diff --git a/src/ui/webviews/configureGitOps/lib/createAzure.ts b/src/webview-backend/configureGitOps/lib/createAzure.ts similarity index 56% rename from src/ui/webviews/configureGitOps/lib/createAzure.ts rename to src/webview-backend/configureGitOps/lib/createAzure.ts index 35bd8405..dd553c8e 100644 --- a/src/ui/webviews/configureGitOps/lib/createAzure.ts +++ b/src/webview-backend/configureGitOps/lib/createAzure.ts @@ -1,17 +1,13 @@ -import { AzureClusterProvider, azureTools, CreateSourceGitAzureArgs } from 'cli/azure/azureTools'; -import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -import { showDeployKeyNotificationIfNeeded } from 'commands/createSource'; -import { telemetry } from 'extension'; -import { ClusterInfo } from 'types/kubernetes/clusterProvider'; -import { Kind } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryEvent } from 'types/telemetryEventNames'; -import { reloadSourcesTreeView, reloadWorkloadsTreeView } from 'ui/treeviews/treeViews'; -import { splitNamespacedFluxObject } from 'utils/namespacedFluxObject'; -import { ParamsDictionary } from 'utils/typeUtils'; +import { AzureClusterProvider, azureTools, CreateSourceBucketAzureArgs, CreateSourceGitAzureArgs } from '../../../azure/azureTools'; +import { showDeployKeyNotificationIfNeeded } from '../../../commands/createSource'; +import { telemetry } from '../../../extension'; +import { ClusterInfo, KubernetesObjectKinds } from '../../../kubernetes/types/kubernetesTypes'; +import { TelemetryEventNames } from '../../../telemetry'; +import { ParamsDictionary } from '../../../utils/typeUtils'; +import { refreshSourcesTreeView, refreshWorkloadsTreeView } from '../../../views/treeViews'; export async function createConfigurationAzure(data: ParamsDictionary) { const clusterInfo = data.clusterInfo as ClusterInfo; - const contextName = kubeConfig.getCurrentContext(); const source = data.source; const kustomization = data.kustomization; @@ -23,15 +19,13 @@ export async function createConfigurationAzure(data: ParamsDictionary) { } } else if(kustomization) { - const gitRepositoryName = splitNamespacedFluxObject(kustomization.source).name; - azureTools.createKustomization(kustomization.name, gitRepositoryName, kustomization.path, - contextName, clusterInfo.clusterProvider as AzureClusterProvider, kustomization.dependsOn, kustomization.prune); + azureTools.createKustomization(kustomization.name, kustomization.source, kustomization.path, + clusterInfo.contextName, clusterInfo.clusterProvider as AzureClusterProvider, kustomization.dependsOn, kustomization.prune); } } async function createGitSourceAzure(source: ParamsDictionary, kustomization: ParamsDictionary, clusterInfo: ClusterInfo) { const args = { - contextName: kubeConfig.getCurrentContext(), sourceName: source.name, url: source.url, ...source, @@ -43,16 +37,16 @@ async function createGitSourceAzure(source: ParamsDictionary, kustomization: Par } as CreateSourceGitAzureArgs; - telemetry.send(TelemetryEvent.CreateSource, { - kind: Kind.GitRepository, + telemetry.send(TelemetryEventNames.CreateSource, { + kind: KubernetesObjectKinds.GitRepository, }); const deployKey = await azureTools.createSourceGit(args); setTimeout(() => { // Wait a bit for the repository to have a failed state in case of SSH url - reloadSourcesTreeView(); - reloadWorkloadsTreeView(); + refreshSourcesTreeView(); + refreshWorkloadsTreeView(); }, 1000); showDeployKeyNotificationIfNeeded(args.url, deployKey); @@ -60,12 +54,11 @@ async function createGitSourceAzure(source: ParamsDictionary, kustomization: Par async function createBucketSourceAzure(source: ParamsDictionary, kustomization: ParamsDictionary, clusterInfo: ClusterInfo) { - telemetry.send(TelemetryEvent.CreateSource, { - kind: Kind.Bucket, + telemetry.send(TelemetryEventNames.CreateSource, { + kind: KubernetesObjectKinds.Bucket, }); const args: any = { - contextName: kubeConfig.getCurrentContext(), sourceName: source.name, url: source.endpoint, configurationName: source.name, @@ -83,8 +76,8 @@ async function createBucketSourceAzure(source: ParamsDictionary, kustomization: await azureTools.createSourceBucket(args); setTimeout(() => { - reloadSourcesTreeView(); - reloadWorkloadsTreeView(); + refreshSourcesTreeView(); + refreshWorkloadsTreeView(); }, 1000); } diff --git a/src/webview-backend/configureGitOps/lib/createGeneric.ts b/src/webview-backend/configureGitOps/lib/createGeneric.ts new file mode 100644 index 00000000..836286eb --- /dev/null +++ b/src/webview-backend/configureGitOps/lib/createGeneric.ts @@ -0,0 +1,29 @@ +import { showDeployKeyNotificationIfNeeded } from '../../../commands/createSource'; +import { telemetry } from '../../../extension'; +import { fluxTools } from '../../../flux/fluxTools'; +import { TelemetryEventNames } from '../../../telemetry'; +import { ParamsDictionary } from '../../../utils/typeUtils'; +import { refreshAllTreeViews, refreshSourcesTreeView } from '../../../views/treeViews'; + +export async function createConfigurationGeneric(data: ParamsDictionary) { + telemetry.send(TelemetryEventNames.CreateSource, { + kind: data.source?.kind, + }); + + + if(data.source) { + const deployKey = await fluxTools.createSource(data.source); + showDeployKeyNotificationIfNeeded(data.source.url, deployKey); + setTimeout(() => { + // Wait a bit for the repository to have a failed state in case of SSH url + refreshSourcesTreeView(); + }, 1000); + + } + + if(data.kustomization) { + await fluxTools.createKustomization(data.kustomization); + } + + refreshAllTreeViews(); +} diff --git a/src/ui/webviews/configureGitOps/lib/exportGeneric.ts b/src/webview-backend/configureGitOps/lib/exportGeneric.ts similarity index 64% rename from src/ui/webviews/configureGitOps/lib/exportGeneric.ts rename to src/webview-backend/configureGitOps/lib/exportGeneric.ts index f7fcf63f..ed94cc30 100644 --- a/src/ui/webviews/configureGitOps/lib/exportGeneric.ts +++ b/src/webview-backend/configureGitOps/lib/exportGeneric.ts @@ -1,11 +1,11 @@ import { window, workspace } from 'vscode'; -import { telemetry } from 'extension'; -import { fluxTools } from 'cli/flux/fluxTools'; -import { TelemetryError, TelemetryEvent } from 'types/telemetryEventNames'; -import { ParamsDictionary } from 'utils/typeUtils'; +import { telemetry } from '../../../extension'; +import { fluxTools } from '../../../flux/fluxTools'; +import { TelemetryErrorEventNames, TelemetryEventNames } from '../../../telemetry'; +import { ParamsDictionary } from '../../../utils/typeUtils'; export async function exportConfigurationGeneric(data: ParamsDictionary) { - telemetry.send(TelemetryEvent.ExportSource, { + telemetry.send(TelemetryEventNames.ExportSource, { kind: data.source?.kind, }); @@ -32,7 +32,7 @@ async function showYaml(text: string) { }, error => { window.showErrorMessage(`Error loading document: ${error}`); - telemetry.sendError(TelemetryError.FAILED_TO_OPEN_RESOURCE); + telemetry.sendError(TelemetryErrorEventNames.FAILED_TO_OPEN_RESOURCE); }); } diff --git a/src/ui/webviews/configureGitOps/openWebview.ts b/src/webview-backend/configureGitOps/openWebview.ts similarity index 58% rename from src/ui/webviews/configureGitOps/openWebview.ts rename to src/webview-backend/configureGitOps/openWebview.ts index 02f2f6b0..aedab718 100644 --- a/src/ui/webviews/configureGitOps/openWebview.ts +++ b/src/webview-backend/configureGitOps/openWebview.ts @@ -1,19 +1,16 @@ import { Uri, window, workspace } from 'vscode'; - -import { GitInfo, getFolderGitInfo } from 'cli/git/gitInfo'; -import { extensionContext, telemetry } from 'extension'; -import { failed } from 'types/errorable'; -import { FluxSourceObject } from 'types/flux/object'; -import { ClusterProvider } from 'types/kubernetes/clusterProvider'; -import { KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { TelemetryEvent } from 'types/telemetryEventNames'; -import { getCurrentClusterInfo } from 'ui/treeviews/treeViews'; -import { namespacedFluxObject } from 'utils/namespacedFluxObject'; +import { failed } from '../../errorable'; +import { telemetry } from '../../extension'; +import { getExtensionContext } from '../../extensionContext'; +import { getFolderGitInfo, GitInfo } from '../../git/gitInfo'; +import { kubernetesTools } from '../../kubernetes/kubernetesTools'; +import { FluxSourceObject, namespacedObject } from '../../kubernetes/types/flux/object'; +import { ClusterProvider, KubernetesObject } from '../../kubernetes/types/kubernetesTypes'; +import { TelemetryEventNames } from '../../telemetry'; +import { getCurrentClusterInfo } from '../../views/treeViews'; import { WebviewBackend } from '../WebviewBackend'; -import { getBuckets, getGitRepositories, getOciRepositories } from 'cli/kubernetes/kubectlGet'; -import { getNamespaces } from 'cli/kubernetes/kubectlGetNamespace'; -import { ConfigureGitOpsWebviewParams } from 'types/webviewParams'; +import { ConfigureGitOpsWebviewParams } from '../types'; import { receiveMessage } from './receiveMessage'; let webview: WebviewBackend | undefined; @@ -23,7 +20,7 @@ let webview: WebviewBackend | undefined; * needed to create a source (and possibly Kustomization) */ export async function openConfigureGitOpsWebview(selectSource: boolean, selectedSource?: FluxSourceObject | string, set?: any, gitInfo?: GitInfo) { - telemetry.send(TelemetryEvent.CreateSourceOpenWebview); + telemetry.send(TelemetryEventNames.CreateSourceOpenWebview); const clusterInfo = await getCurrentClusterInfo(); if (failed(clusterInfo)) { @@ -43,22 +40,19 @@ export async function openConfigureGitOpsWebview(selectSource: boolean, selected gitInfo = await getFolderGitInfo(workspace.workspaceFolders[0].uri.fsPath); } - const [nsResults, gitResults, ociResults, bucketResults] = await Promise.all([ - getNamespaces(), - getGitRepositories(), - getOciRepositories(), - getBuckets(), + const [nsResults, gitResults, ociResults, bucketResults] = await Promise.all([kubernetesTools.getNamespaces(), + kubernetesTools.getGitRepositories(), + kubernetesTools.getOciRepositories(), + kubernetesTools.getBuckets(), ]); - const namespaces = nsResults.map(i => i.metadata.name) as string[]; + const namespaces = nsResults?.items.map(i => i.metadata.name) as string[]; - const sources: KubernetesObject[] = [ - ...gitResults, - ...ociResults, - ...bucketResults, - ]; + const sources: KubernetesObject[] = [...gitResults?.items || [], + ...ociResults?.items || [], + ...bucketResults?.items || []]; - const selectedSourceName = typeof selectedSource === 'string' ? selectedSource : (namespacedFluxObject(selectedSource) || ''); + const selectedSourceName = typeof selectedSource === 'string' ? selectedSource : (namespacedObject(selectedSource) || ''); const webviewParams: ConfigureGitOpsWebviewParams = { clusterInfo: clusterInfo.result, @@ -72,7 +66,7 @@ export async function openConfigureGitOpsWebview(selectSource: boolean, selected if(!webview || webview.disposed) { - const extensionUri = extensionContext.extensionUri; + const extensionUri = getExtensionContext().extensionUri; const uri = Uri.joinPath(extensionUri, 'webview-ui', 'configureGitOps'); webview = new WebviewBackend('Configure GitOps', uri, webviewParams, receiveMessage); } else { diff --git a/src/ui/webviews/configureGitOps/receiveMessage.ts b/src/webview-backend/configureGitOps/receiveMessage.ts similarity index 99% rename from src/ui/webviews/configureGitOps/receiveMessage.ts rename to src/webview-backend/configureGitOps/receiveMessage.ts index 0cd47e6a..48acdd05 100644 --- a/src/ui/webviews/configureGitOps/receiveMessage.ts +++ b/src/webview-backend/configureGitOps/receiveMessage.ts @@ -1,5 +1,4 @@ import { WebviewPanel } from 'vscode'; - import { actionCreate, actionYAML } from './actions'; export async function receiveMessage(message: any, panel: WebviewPanel) { diff --git a/src/ui/webviews/createFromTemplate/openWebview.ts b/src/webview-backend/createFromTemplate/openWebview.ts similarity index 84% rename from src/ui/webviews/createFromTemplate/openWebview.ts rename to src/webview-backend/createFromTemplate/openWebview.ts index dcec1705..0cd11bbe 100644 --- a/src/ui/webviews/createFromTemplate/openWebview.ts +++ b/src/webview-backend/createFromTemplate/openWebview.ts @@ -1,7 +1,6 @@ import { Uri, window } from 'vscode'; - -import { extensionContext } from 'extension'; -import { GitOpsTemplate } from 'types/flux/gitOpsTemplate'; +import { getExtensionContext } from '../../extensionContext'; +import { GitOpsTemplate } from '../../kubernetes/types/flux/gitOpsTemplate'; import { WebviewBackend } from '../WebviewBackend'; import { receiveMessage } from './receiveMessage'; @@ -30,7 +29,7 @@ export async function openCreateFromTemplatePanel(template: GitOpsTemplate) { webview?.dispose(); - const extensionUri = extensionContext.extensionUri; + const extensionUri = getExtensionContext().extensionUri; const uri = Uri.joinPath(extensionUri, 'webview-ui', 'createFromTemplate'); webview = new WebviewBackend('Create from Template', uri, webviewParams, receiveMessage); } diff --git a/src/ui/webviews/createFromTemplate/receiveMessage.ts b/src/webview-backend/createFromTemplate/receiveMessage.ts similarity index 91% rename from src/ui/webviews/createFromTemplate/receiveMessage.ts rename to src/webview-backend/createFromTemplate/receiveMessage.ts index cca11a9b..603f1199 100644 --- a/src/ui/webviews/createFromTemplate/receiveMessage.ts +++ b/src/webview-backend/createFromTemplate/receiveMessage.ts @@ -1,10 +1,13 @@ -import * as shell from 'cli/shell/exec'; -import { v4 as uuidv4 } from 'uuid'; import { Uri, WebviewPanel, workspace } from 'vscode'; +import { shell } from '../../shell'; +import { v4 as uuidv4 } from 'uuid'; + export async function receiveMessage(message: any, panel: WebviewPanel) { switch (message.action) { case 'show-yaml': + // actionYAML(message.data); + console.log(message.data); const data = message.data; renderTemplates(data.template, data.values); diff --git a/src/types/webviewParams.ts b/src/webview-backend/types.ts similarity index 67% rename from src/types/webviewParams.ts rename to src/webview-backend/types.ts index a8d1348d..0c2005fa 100644 --- a/src/types/webviewParams.ts +++ b/src/webview-backend/types.ts @@ -1,7 +1,6 @@ -import { GitInfo } from 'cli/git/gitInfo'; -import { TemplateParam } from 'types/flux/gitOpsTemplate'; -import { KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { ClusterInfo } from './kubernetes/clusterProvider'; +import { GitInfo } from '../git/gitInfo'; +import { GitOpsTemplate, TemplateParam } from '../kubernetes/types/flux/gitOpsTemplate'; +import { ClusterInfo, KubernetesObject } from '../kubernetes/types/kubernetesTypes'; export type ConfigureGitOpsWebviewParams = { clusterInfo: ClusterInfo; diff --git a/tsconfig.json b/tsconfig.json index 4eb511ad..41c07d51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,7 @@ "rootDir": "src", "strict": true, /* enable all strict type-checking options */ "forceConsistentCasingInFileNames": true, - "esModuleInterop": true, - "baseUrl": "./src", + "esModuleInterop": true }, "exclude": [ "node_modules", diff --git a/tslint-imports.json b/tslint-imports.json deleted file mode 100644 index 7929f6ec..00000000 --- a/tslint-imports.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": [ - "tslint-etc" - ], - "rules": { - "no-unused-declaration": true - } -} -// https://wesleygrimes.com/angular/2019/02/14/how-to-use-tslint-to-autoremove-all-unused-imports-in-a-typescript-project -// npm install -g typescript tslint tslint-etc -// tslint --config tslint-imports.json --fix --project . \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index fb874fed..c6742823 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,8 +4,6 @@ 'use strict'; const path = require('path'); -const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); - /**@type {import('webpack').Configuration}*/ const config = { @@ -28,11 +26,6 @@ const config = { resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: ['.ts', '.js'], - plugins: [ - // @ts-ignore - new TsconfigPathsPlugin({ - configFile: './tsconfig.json'}), - ], }, module: { rules: [ diff --git a/webview-ui/configureGitOps/package-lock.json b/webview-ui/configureGitOps/package-lock.json index 4cb7449d..0d34e9b9 100644 --- a/webview-ui/configureGitOps/package-lock.json +++ b/webview-ui/configureGitOps/package-lock.json @@ -1410,9 +1410,9 @@ "dev": true }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -1541,15 +1541,15 @@ } }, "node_modules/vite": { - "version": "2.9.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz", - "integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==", + "version": "2.9.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz", + "integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==", "dev": true, "dependencies": { "esbuild": "^0.14.27", "postcss": "^8.4.13", "resolve": "^1.22.0", - "rollup": ">=2.59.0 <2.78.0" + "rollup": "^2.59.0" }, "bin": { "vite": "bin/vite.js" @@ -2510,9 +2510,9 @@ "dev": true }, "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "solid-collapse": { @@ -2597,16 +2597,16 @@ "dev": true }, "vite": { - "version": "2.9.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz", - "integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==", + "version": "2.9.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz", + "integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==", "dev": true, "requires": { "esbuild": "^0.14.27", "fsevents": "~2.3.2", "postcss": "^8.4.13", "resolve": "^1.22.0", - "rollup": ">=2.59.0 <2.78.0" + "rollup": "^2.59.0" } }, "vite-plugin-solid": { diff --git a/webview-ui/configureGitOps/src/components/Source/NewSource.tsx b/webview-ui/configureGitOps/src/components/Source/NewSource.tsx index f391190e..a8c4e0fa 100644 --- a/webview-ui/configureGitOps/src/components/Source/NewSource.tsx +++ b/webview-ui/configureGitOps/src/components/Source/NewSource.tsx @@ -2,7 +2,7 @@ import { Tabs } from '@microsoft/fast-foundation'; import { ToolkitHelpLink } from 'components/Common/HelpLink'; import { params } from 'lib/params'; import { onMount, Show } from 'solid-js'; -import { setSource, source } from 'lib/model'; +import { setSource, source } from '../../lib/model'; import Bucket from './NewSource/Bucket'; import GitRepository from './NewSource/GitRepository'; diff --git a/webview-ui/configureGitOps/src/components/Source/NewSource/HelmRepository.tsx b/webview-ui/configureGitOps/src/components/Source/NewSource/HelmRepository.tsx index 5aabb4eb..5443d2d3 100644 --- a/webview-ui/configureGitOps/src/components/Source/NewSource/HelmRepository.tsx +++ b/webview-ui/configureGitOps/src/components/Source/NewSource/HelmRepository.tsx @@ -1,6 +1,6 @@ import TextInput from 'components/Common/TextInput'; -import { helmRepository, source } from 'lib/model'; +import { helmRepository, source } from '../../../lib/model'; import Name from './Common/Name'; import Namespace from './Common/Namespace'; diff --git a/webview-ui/configureGitOps/src/components/Source/NewSource/Settings/HelmRepository/HelmConnection.tsx b/webview-ui/configureGitOps/src/components/Source/NewSource/Settings/HelmRepository/HelmConnection.tsx index fd7c4ba9..4595c5e4 100644 --- a/webview-ui/configureGitOps/src/components/Source/NewSource/Settings/HelmRepository/HelmConnection.tsx +++ b/webview-ui/configureGitOps/src/components/Source/NewSource/Settings/HelmRepository/HelmConnection.tsx @@ -1,7 +1,7 @@ import { Show } from 'solid-js'; import TextInput from 'components/Common/TextInput'; -import { isOCIHelm } from 'components/Source/NewSource/HelmRepository'; +import { isOCIHelm } from '../../HelmRepository'; import Checkbox from 'components/Common/Checkbox'; import { source } from 'lib/model'; import { ToolkitHelpLink } from 'components/Common/HelpLink'; diff --git a/webview-ui/configureGitOps/src/lib/model.ts b/webview-ui/configureGitOps/src/lib/model.ts index b081752e..aa4f56a6 100644 --- a/webview-ui/configureGitOps/src/lib/model.ts +++ b/webview-ui/configureGitOps/src/lib/model.ts @@ -85,7 +85,7 @@ export const [createWorkload, setCreateWorkload] = createSignal(false); export const [kustomization, setKustomization] = createStore({ name: 'podinfo', namespace: 'flux-system', - source: '', // Ex: GitRepository/podinfo.flux-system + source: '', // Ex: GitRepo/podinfo.flux-system path: '/kustomize', targetNamespace: 'default', serviceAccount: '', @@ -135,7 +135,12 @@ createEffect(() => { if(params.selectSourceTab) { setCreateWorkload(true); - updateSelectedSource(); + + if(params.selectedSource && params.selectedSource !== '') { + setKustomization('source', params.selectedSource); + } else if(params.sources?.length > 0) { + setKustomization('source', namespacedSource(params.sources[0])); + } } if(params.set) { @@ -157,7 +162,8 @@ createEffect(() => { if(createSource()) { setKustomization('source', `${source.kind}/${source.name}.${source.namespace}`); } else { - updateSelectedSource(); + const s = params.sources[0]; + setKustomization('source', `${s.kind}/${s.name}.${s.namespace}`); } }); @@ -195,14 +201,6 @@ const setters: StoreMap = { setKustomization, }; -export function updateSelectedSource() { - if (params.selectedSource && params.selectedSource !== '') { - setKustomization('source', params.selectedSource); - } else if (params.sources?.length > 0) { - setKustomization('source', namespacedSource(params.sources[0])); - } -} - export function storeAccessors(props: any) { let get: ()=> any; let set: (v: any)=> any; diff --git a/webview-ui/createFromTemplate/package-lock.json b/webview-ui/createFromTemplate/package-lock.json index 75d188e3..5d384218 100644 --- a/webview-ui/createFromTemplate/package-lock.json +++ b/webview-ui/createFromTemplate/package-lock.json @@ -1008,10 +1008,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "6.3.0", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -1127,15 +1126,14 @@ } }, "node_modules/vite": { - "version": "2.9.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz", - "integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==", + "version": "2.9.13", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.14.27", "postcss": "^8.4.13", "resolve": "^1.22.0", - "rollup": ">=2.59.0 <2.78.0" + "rollup": "^2.59.0" }, "bin": { "vite": "bin/vite.js"