diff --git a/.github/workflows/build-vsix.yml b/.github/workflows/build-vsix.yml index 85712321..2141aed7 100644 --- a/.github/workflows/build-vsix.yml +++ b/.github/workflows/build-vsix.yml @@ -235,7 +235,7 @@ jobs: # - name: Get the version # id: version -# run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} +# run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT # shell: bash # # - uses: apexskier/github-semver-parse@671ddf80785e4d721e76723ec1e687317f85bfe9 # pin@v1 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/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 af8e79bb..dc64040e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,34 +9,23 @@ "version": "0.26.1-edge.0", "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 d52b5fa9..1848b664 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,12 @@ "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"