diff --git a/client/package.json b/client/package.json index 9677276b..5ef8a6b4 100644 --- a/client/package.json +++ b/client/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@mdi/font": "7.0.96", + "@wdns/vue-code-block": "^2.3.3", "apexcharts": "^3.49.0", "axios": "^1.7.4", "chart.js": "^4.4.1", @@ -28,7 +29,8 @@ "vue3-apexcharts": "^1.5.2", "vue3-cookies": "^1.0.6", "vuetify": "^3.4.0", - "xterm": "^5.3.0" + "xterm": "^5.3.0", + "yaml": "^2.6.1" }, "devDependencies": { "@babel/types": "^7.21.4", diff --git a/client/src/components/pipelines/list.vue b/client/src/components/pipelines/list.vue index 8846649d..78cb71ad 100644 --- a/client/src/components/pipelines/list.vue +++ b/client/src/components/pipelines/list.vue @@ -10,13 +10,14 @@ New Pipeline - + Create your first pipeline + + +

Kubero can't reach your kubernetes cluster. Please proceed with the setup to continue.

+ + Start Setup +
+
@@ -113,6 +130,7 @@ import axios from "axios"; import { ref, defineComponent } from 'vue' import Breadcrumbs from "../breadcrumbs.vue"; import { useKuberoStore } from '../../stores/kubero' +import { mapState } from 'pinia' import Swal from 'sweetalert2'; type Pipeline = { @@ -172,6 +190,9 @@ export default defineComponent({ } ], }}, + computed: { + ...mapState(useKuberoStore, ['kubero']), + }, methods: { async loadPipelinesList() { const self = this; diff --git a/client/src/components/setup/index.vue b/client/src/components/setup/index.vue new file mode 100644 index 00000000..a92e5fe4 --- /dev/null +++ b/client/src/components/setup/index.vue @@ -0,0 +1,545 @@ + + + + + \ No newline at end of file diff --git a/client/src/layouts/setup/Setup.vue b/client/src/layouts/setup/Setup.vue new file mode 100644 index 00000000..8946e389 --- /dev/null +++ b/client/src/layouts/setup/Setup.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/client/src/layouts/setup/View.vue b/client/src/layouts/setup/View.vue new file mode 100644 index 00000000..ac4db202 --- /dev/null +++ b/client/src/layouts/setup/View.vue @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/client/src/plugins/code-block.ts b/client/src/plugins/code-block.ts new file mode 100644 index 00000000..b278d3a9 --- /dev/null +++ b/client/src/plugins/code-block.ts @@ -0,0 +1,5 @@ +import { createVCodeBlock } from '@wdns/vue-code-block'; + +export default createVCodeBlock({ + // options +}); \ No newline at end of file diff --git a/client/src/plugins/index.ts b/client/src/plugins/index.ts index ecc16c1a..1b6a4365 100644 --- a/client/src/plugins/index.ts +++ b/client/src/plugins/index.ts @@ -8,6 +8,7 @@ import vuetify from './vuetify' import router from '../router' import pinia from './pinia' +import vCodeBlock from './code-block' // Types import type { App } from 'vue' @@ -15,6 +16,8 @@ import type { App } from 'vue' export function registerPlugins (app: App) { app .use(pinia) + // @ts-ignore: Type missmatch + .use(vCodeBlock) .use(vuetify) .use(router) } diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 088b43ed..5c0a9607 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -95,6 +95,17 @@ const routes = [ }, ], }, + { + path: '/setup', + component: () => import('@/layouts/setup/Setup.vue'), + children: [ + { + path: '/setup', + name: 'Setup', + component: () => import('@/views/Setup.vue'), + }, + ], + }, { path: '/popup', component: () => import('@/layouts/default/Popup.vue'), diff --git a/client/src/views/Setup.vue b/client/src/views/Setup.vue new file mode 100644 index 00000000..8eeff2cd --- /dev/null +++ b/client/src/views/Setup.vue @@ -0,0 +1,7 @@ + + + diff --git a/client/yarn.lock b/client/yarn.lock index 9f3e0f36..c7831e26 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -476,6 +476,17 @@ path-browserify "^1.0.1" vscode-uri "^3.0.8" +"@vue/compiler-core@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05" + integrity sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q== + dependencies: + "@babel/parser" "^7.25.3" + "@vue/shared" "3.5.13" + entities "^4.5.0" + estree-walker "^2.0.2" + source-map-js "^1.2.0" + "@vue/compiler-core@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.9.tgz#d51fbfe6c18479b27fe6b1723344ba0832e4aacb" @@ -487,6 +498,14 @@ estree-walker "^2.0.2" source-map-js "^1.2.0" +"@vue/compiler-dom@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58" + integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA== + dependencies: + "@vue/compiler-core" "3.5.13" + "@vue/shared" "3.5.13" + "@vue/compiler-dom@3.5.9", "@vue/compiler-dom@^3.4.0": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.9.tgz#6fa2b7e536ae4c416fc2d60b7e9e33b3410eac7a" @@ -495,6 +514,21 @@ "@vue/compiler-core" "3.5.9" "@vue/shared" "3.5.9" +"@vue/compiler-sfc@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46" + integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ== + dependencies: + "@babel/parser" "^7.25.3" + "@vue/compiler-core" "3.5.13" + "@vue/compiler-dom" "3.5.13" + "@vue/compiler-ssr" "3.5.13" + "@vue/shared" "3.5.13" + estree-walker "^2.0.2" + magic-string "^0.30.11" + postcss "^8.4.48" + source-map-js "^1.2.0" + "@vue/compiler-sfc@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.9.tgz#020b7654f1fde7c606a49ec4e4d2838e8e1a43c5" @@ -510,6 +544,14 @@ postcss "^8.4.47" source-map-js "^1.2.0" +"@vue/compiler-ssr@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz#e771adcca6d3d000f91a4277c972a996d07f43ba" + integrity sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA== + dependencies: + "@vue/compiler-dom" "3.5.13" + "@vue/shared" "3.5.13" + "@vue/compiler-ssr@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.9.tgz#e30f8e866589392421abcbfc0e0241470f3ca9a6" @@ -554,6 +596,13 @@ muggle-string "^0.4.1" path-browserify "^1.0.1" +"@vue/reactivity@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f" + integrity sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg== + dependencies: + "@vue/shared" "3.5.13" + "@vue/reactivity@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.9.tgz#8864a55e4c495666f3c679beb8f734489eeb042e" @@ -561,6 +610,14 @@ dependencies: "@vue/shared" "3.5.9" +"@vue/runtime-core@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz#1fafa4bf0b97af0ebdd9dbfe98cd630da363a455" + integrity sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw== + dependencies: + "@vue/reactivity" "3.5.13" + "@vue/shared" "3.5.13" + "@vue/runtime-core@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.9.tgz#e47f890734039f77dac86328cc059cf8188c5729" @@ -569,6 +626,16 @@ "@vue/reactivity" "3.5.9" "@vue/shared" "3.5.9" +"@vue/runtime-dom@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz#610fc795de9246300e8ae8865930d534e1246215" + integrity sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog== + dependencies: + "@vue/reactivity" "3.5.13" + "@vue/runtime-core" "3.5.13" + "@vue/shared" "3.5.13" + csstype "^3.1.3" + "@vue/runtime-dom@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.9.tgz#088746207f74963d09b31ce7b79add0bf96aa337" @@ -579,6 +646,14 @@ "@vue/shared" "3.5.9" csstype "^3.1.3" +"@vue/server-renderer@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz#429ead62ee51de789646c22efe908e489aad46f7" + integrity sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA== + dependencies: + "@vue/compiler-ssr" "3.5.13" + "@vue/shared" "3.5.13" + "@vue/server-renderer@3.5.9": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.9.tgz#3bf0736001623960d120ef01dee5045fad6efadb" @@ -587,6 +662,11 @@ "@vue/compiler-ssr" "3.5.9" "@vue/shared" "3.5.9" +"@vue/shared@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" + integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== + "@vue/shared@3.5.9", "@vue/shared@^3.4.0": version "3.5.9" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.9.tgz#713257216ea2cbf4e200cb9ae395c34ae2349385" @@ -600,6 +680,16 @@ find-cache-dir "^3.3.2" upath "^2.0.1" +"@wdns/vue-code-block@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@wdns/vue-code-block/-/vue-code-block-2.3.3.tgz#8b9ade0530e9b710e44b7b1ba5a4a48d6a71cf06" + integrity sha512-eOsCTatfi/8/zcgk7yzjuu+t4Ms4Te9SwYUE5PA/+JYcgp+JXAnYBgvqwPFVoTVKq3IpQCiGZg2zMblssvUCUQ== + dependencies: + highlight.js "^11.8.0" + prismjs "^1.29.0" + ua-parser-js "^1.0.38" + vue "^3.4.31" + "@yr/monotone-cubic-spline@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz#7272d89f8e4f6fb7a1600c28c378cc18d3b577b9" @@ -1174,6 +1264,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +highlight.js@^11.8.0: + version "11.10.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92" + integrity sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ== + ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -1477,6 +1572,11 @@ picocolors@^1.1.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -1514,11 +1614,25 @@ postcss@^8.4.43, postcss@^8.4.47: picocolors "^1.1.0" source-map-js "^1.2.1" +postcss@^8.4.48: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prismjs@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" @@ -1777,6 +1891,11 @@ typescript@^5.0.0: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== +ua-parser-js@^1.0.38: + version "1.0.39" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.39.tgz#bfc07f361549bf249bd8f4589a4cccec18fd2018" + integrity sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -1909,6 +2028,17 @@ vue@^3.0.0, vue@^3.4.0: "@vue/server-renderer" "3.5.9" "@vue/shared" "3.5.9" +vue@^3.4.31: + version "3.5.13" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a" + integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ== + dependencies: + "@vue/compiler-dom" "3.5.13" + "@vue/compiler-sfc" "3.5.13" + "@vue/runtime-dom" "3.5.13" + "@vue/server-renderer" "3.5.13" + "@vue/shared" "3.5.13" + vuetify@^3.4.0: version "3.7.2" resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.7.2.tgz#e37fa4c191ea00144de5943315cbf77ddb80448d" @@ -1956,6 +2086,11 @@ xterm@^5.3.0: resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46" integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== +yaml@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" diff --git a/server/src/kubero.ts b/server/src/kubero.ts index e4456b15..3969286b 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -944,7 +944,8 @@ export class Kubero { } } } catch (error) { - console.log('Error: getSleepEnabled: ', error) + console.log('❌ getSleepEnabled: could not check for Zeropod') + //console.log(error) return false } diff --git a/server/src/modules/kubectl.ts b/server/src/modules/kubectl.ts index cd4311e1..38a50173 100644 --- a/server/src/modules/kubectl.ts +++ b/server/src/modules/kubectl.ts @@ -54,29 +54,35 @@ export class Kubectl { private exec: Exec = {} as Exec; constructor() { - //this.config = config; this.kc = new KubeConfig(); + this.log = new KubeLog(this.kc); + this.kubeVersion = new VersionInfo(); + this.initKubeConfig(); + } + + private initKubeConfig() { + //this.config = config; //this.kc.loadFromDefault(); // should not be used since we want also load from base64 ENV var if (process.env.KUBECONFIG_BASE64) { - debug.log("Use kubectl config from base64"); let buff = Buffer.from(process.env.KUBECONFIG_BASE64, 'base64'); const kubeconfig = buff.toString('ascii'); this.kc.loadFromString(kubeconfig); + + debug.log("ℹ️ Kubeconfig loaded from base64"); } else if(process.env.KUBECONFIG_PATH) { - debug.log("Use kubectl config from file " + process.env.KUBECONFIG_PATH); this.kc.loadFromFile(process.env.KUBECONFIG_PATH); + debug.log("ℹ️ Kubeconfig loaded from file " + process.env.KUBECONFIG_PATH); } else{ try { this.kc.loadFromCluster(); - debug.log("Kubeconfig loaded from cluster"); + debug.log("ℹ️ Kubeconfig loaded from cluster"); } catch (error) { debug.log("❌ Error loading from cluster"); - debug.log(error); + //debug.log(error); } } - this.log = new KubeLog(this.kc); try { this.versionApi = this.kc.makeApiClient(VersionApi); @@ -90,20 +96,25 @@ export class Kubectl { this.exec = new Exec(this.kc) this.customObjectsApi = this.kc.makeApiClient(CustomObjectsApi); } catch (error) { - debug.log("❌ Error creating api clients. Check kubeconfig, cluster connectivity and context"); - debug.log(error); + console.log("❌ Error creating api clients. Check kubeconfig, cluster connectivity and context"); + //debug.log(error); this.kubeVersion = void 0; return; } - this.kubeVersion = new VersionInfo(); this.getKubeVersion() .then(v => { + if (v && v.gitVersion) { + console.log("ℹ️ Kube version: " + v.gitVersion); + } else { + console.log("❌ Failed to get Kubernetes version"); + process.env.KUBERO_SETUP = 'enabled'; + } this.kubeVersion = v; }) .catch(error => { - debug.log("❌ Error getting kube version"); - debug.log(error); + console.log("❌ Failed to get Kubernetes version"); + //debug.log(error); }); this.getOperatorVersion() @@ -111,14 +122,13 @@ export class Kubectl { debug.log("ℹ️ Operator version: " + v); this.kuberoOperatorVersion = v || 'unknown'; }) - } public async getKubeVersion(): Promise{ // TODO and WARNING: This does not respect the context set by the user! try { let versionInfo = await this.versionApi.getCode() - debug.debug(JSON.stringify(versionInfo.body)); + //debug.debug(JSON.stringify(versionInfo.body)); return versionInfo.body; } catch (error) { debug.log("getKubeVersion: error getting kube version"); @@ -133,7 +143,8 @@ export class Kubectl { if (contextName) { const pods = await this.getPods(namespace, contextName) .catch(error => { - debug.log("Failed to get Operator Version", error); + debug.log("❌ Failed to get Operator Version"); + //debug.log(error); //return 'error'; }); if (pods) { @@ -178,7 +189,7 @@ export class Kubectl { return pipelines.body as IKubectlPipelineList; } catch (error) { //debug.log(error); - debug.log("getPipelinesList: error getting pipelines"); + debug.log("❌ getPipelinesList: error getting pipelines"); } const pipelines = {} as IKubectlPipelineList; pipelines.items = []; @@ -197,7 +208,8 @@ export class Kubectl { "kuberopipelines", pipeline ).catch(error => { - debug.log(error); + debug.log("❌ Error creating pipeline: " + pl.name); + //debug.log(error); }); } @@ -215,7 +227,8 @@ export class Kubectl { pl.name, pipeline ).catch(error => { - debug.log(error); + debug.log("❌ Error updating pipeline: " + pl.name); + //debug.log(error); }); } @@ -243,7 +256,8 @@ export class Kubectl { "kuberopipelines", pipelineName ).catch(error => { - debug.log(error); + //debug.log(error); + debug.log("getPipeline: error getting pipeline"); throw error; }); if (pipeline) { @@ -343,7 +357,7 @@ export class Kubectl { ) return appslist.body as IKubectlAppList; } catch (error) { - debug.log(error); + //debug.log(error); debug.log("getAppsList: error getting apps"); } const appslist = {} as IKubectlAppList; @@ -409,7 +423,7 @@ export class Kubectl { //let operators = response.body as KubernetesListObject; operators = response.body as any // TODO : fix type. This is a hacky way to get the type to work } catch (error) { - debug.log(error); + //debug.log(error); debug.log("error getting operators"); } @@ -1074,7 +1088,7 @@ export class Kubectl { //console.log(config.body); return config.body; } catch (error) { - debug.log(error); + //debug.log(error); debug.log("getKuberoConfig: error getting config"); } } @@ -1227,4 +1241,96 @@ export class Kubectl { debug.log("getJobs: error getting jobs"); } } + + public async validateKubeconfig(kubeconfig: string, kubeContext: string): Promise<{error: any, valid: boolean}> { + // validate config for setup process + + //let buff = Buffer.from(configBase64, 'base64'); + //const kubeconfig = buff.toString('ascii'); + + const kc = new KubeConfig(); + kc.loadFromString(kubeconfig); + kc.setCurrentContext(kubeContext); + + try { + const versionApi = kc.makeApiClient(VersionApi); + let versionInfo = await versionApi.getCode() + console.log(JSON.stringify(versionInfo.body)); + return { error: null, valid: true }; + } catch (error: any) { + console.log("Error validating kubeconfig: " + error); + console.log(error); + return {error: error.message, valid: false}; + } + } + + public updateKubectlConfig(kubeconfig: string, kubeContext: string) { + // update kubeconfig in the kubectl instance + /* + this.kc.loadFromString(kubeconfig); + this.kc.setCurrentContext(kubeContext); + */ + this.initKubeConfig(); + console.log(kubeContext, this.kc.getCurrentContext()); + + console.log("Kubeconfig updated"); + } + + public async checkNamespace(namespace: string): Promise { + try { + const ns = await this.coreV1Api.readNamespace(namespace); + return true; + } catch (error) { + return false; + } + } + + public async checkPod(namespace: string, podName: string): Promise { + try { + const pod = await this.coreV1Api.readNamespacedPod(podName, namespace); + return true; + } catch (error) { + return false; + } + } + + public async checkDeployment(namespace: string, deploymentName: string): Promise { + try { + const deployment = await this.appsV1Api.readNamespacedDeployment(deploymentName, namespace); + return true; + } catch (error) { + return false; + } + } + + public async checkCustomResourceDefinition(plural: string): Promise { + try { + const crd = await this.customObjectsApi.listClusterCustomObject( + 'apiextensions.k8s.io', + 'v1', + plural + ); + return true; + } catch (error) { + console.log(error); + return false; + } + } + + public async createNamespace(namespace: string): Promise { + const ns = { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name: namespace + } + } + try { + return await this.coreV1Api.createNamespace(ns); + } catch (error) { + //console.log(error); + console.log('ERROR creating namespace'); + } + } + } \ No newline at end of file diff --git a/server/src/modules/settings.ts b/server/src/modules/settings.ts index 19a904d2..49a04c49 100644 --- a/server/src/modules/settings.ts +++ b/server/src/modules/settings.ts @@ -42,14 +42,14 @@ export class Settings { const namespace = process.env.KUBERO_NAMESPACE || "kubero" let kuberoes = await this.kubectl.getKuberoConfig(namespace) -/* + let configMap: KuberoConfig if (process.env.NODE_ENV === "production") { configMap = new KuberoConfig(kuberoes.spec.kubero.config) } else { configMap = new KuberoConfig(this.readConfig()) } -*/ + let config: any = {} config.settings = kuberoes.spec @@ -176,10 +176,33 @@ export class Settings { } public async getDefaultRegistry(): Promise { - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - let kuberoes = await this.kubectl.getKuberoConfig(namespace) + + let registry = process.env.KUBERO_REGISTRY || { + account:{ + hash: '$2y$05$czQZpvtDYc5OzM/1r1pH0eAplT/okohh/mXoWl/Y65ZP/8/jnSWZq', + password: 'kubero', + username: 'kubero', + + }, + create: false, + enabled: false, + host: 'registry.demo.kubero.dev', + port: 443, + storage: '1Gi', + storageClassName: null, + subpath: "", + + } + try { + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + const kuberoes = await this.kubectl.getKuberoConfig(namespace) + registry = kuberoes.spec.registry + } catch (error) { + console.log("Error getting kuberoes config") + } + return registry + - return kuberoes.spec.registry } public async getDomains(): Promise { @@ -196,4 +219,76 @@ export class Settings { private checkAdminDisabled() { return this.runningConfig.kubero.admin?.disabled || false } + + public async validateKubeconfig(kubeConfig: string, kubeContext: string): Promise { + if (process.env.KUBERO_SETUP != "enabled") { + return { + error: "Setup is disabled. Set env KUBERO_SETUP=enabled and retry", + status: "error" + } + } + return this.kubectl.validateKubeconfig(kubeConfig, kubeContext) + } + + public updateRunningConfig(kubeConfig: string, kubeContext: string, kuberoNamespace: string, KuberoSessionKey: string, kuberoWebhookSecret: string): {error: string, status: string} { + + if (process.env.KUBERO_SETUP != "enabled") { + return { + error: "Setup is disabled. Set env KUBERO_SETUP=enabled and retry", + status: "error" + } + } + + process.env.KUBERO_CONTEXT = kubeContext + process.env.KUBERO_NAMESPACE = kuberoNamespace + process.env.KUBERO_SESSION_KEY = KuberoSessionKey + process.env.KUBECONFIG_BASE64 = kubeConfig + process.env.KUBERO_SETUP = "disabled" + + this.kubectl.updateKubectlConfig(kubeConfig, kubeContext) + + this.kubectl.createNamespace(kuberoNamespace) + return { + error: "", + status: "ok" + } + } + + public async checkComponent(component: string): Promise { + let ret = { + //reason : "Component not found", + status: "error" + } + + if (component === "operator") { + //let operator = await this.kubectl.checkCustomResourceDefinition("kuberoes.application.kubero.dev") + let operator = await this.kubectl.checkNamespace("kubero-operator-system") + if (operator) { + ret.status = "ok" + } + } + + if (component === "metrics") { + let metrics = await this.kubectl.checkDeployment("kube-system", "metrics-server") + if (metrics) { + ret.status = "ok" + } + } + + if (component === "debug") { + let metrics = await this.kubectl.checkNamespace("default") + if (metrics) { + ret.status = "ok" + } + } + + if (component === "ingress") { + let ingress = await this.kubectl.checkNamespace("ingress-nginx") + if (ingress) { + ret.status = "ok" + } + } + + return ret + } } \ No newline at end of file diff --git a/server/src/routes/config.ts b/server/src/routes/config.ts index 15a7a4cb..f1b5ae6a 100644 --- a/server/src/routes/config.ts +++ b/server/src/routes/config.ts @@ -10,6 +10,7 @@ export const bearerMiddleware = auth.getBearerMiddleware(); import debug from 'debug'; +import { spawn } from 'child_process'; debug('app:routes') Router.get('/config', authMiddleware, async function (req: Request, res: Response) { @@ -66,6 +67,55 @@ Router.get('/config/registry', authMiddleware, async function (req: Request, res res.send(await req.app.locals.settings.getDefaultRegistry()); }); +Router.post('/config/k8s/kubeconfig/validate', authMiddleware, async function (req: Request, res: Response) { + // #swagger.tags = ['UI'] + // #swagger.summary = 'Validate the kubeconfig' + // #swagger.description = 'Validate the kubeconfig for setup process' + // #swagger.parameters['kubeconfig'] = { description: 'Kubeconfig' } + const kubeconfig = req.body.kubeconfig; + const kubeContext = req.body.context; + const result = await req.app.locals.settings.validateKubeconfig(kubeconfig, kubeContext); + res.send(result); +}); + +Router.post('/config/setup/save', authMiddleware, async function (req: Request, res: Response) { + // #swagger.tags = ['UI'] + // #swagger.summary = 'Save the initial Kubero configuration' + // #swagger.description = 'Save the initial Kubero configuration' + // #swagger.parameters['KUBECONFIG_BASE64'] = { description: 'Base 64 encoded Kubeconfig' } + // #swagger.parameters['KUBERO_CONTEXT'] = { description: 'Kubernetes context' } + // #swagger.parameters['KUBERO_NAMESPACE'] = { description: 'Kubero namespace' } + // #swagger.parameters['KUBERO_SESSION_KEY'] = { description: 'Kubero UI session key' } + const kubeconfigBase64 = req.body.KUBECONFIG_BASE64; + const kubeContext = req.body.KUBERO_CONTEXT; + const kuberoNamespace = req.body.KUBERO_NAMESPACE; + const KuberoSessionKey = req.body.KUBERO_SESSION_KEY; + const kuberoWebhookSecret = req.body.KUBERO_WEBHOOK_SECRET; + + // Base64 decode the kubeconfig + const kubeconfigDecoded = Buffer.from(kubeconfigBase64, 'base64').toString('utf-8'); + const resultValidation = await req.app.locals.settings.validateKubeconfig(kubeconfigDecoded, kubeContext); + if (resultValidation.valid === false) { + res.send(resultValidation); + return; + } + + const resultUpdateConfig = await req.app.locals.settings.updateRunningConfig(kubeconfigBase64, kubeContext, kuberoNamespace, KuberoSessionKey, kuberoWebhookSecret); + + req.app.locals.kubero.updateState(); + + res.send(resultUpdateConfig); +}); + +Router.get('/config/setup/check/:component', authMiddleware, async function (req: Request, res: Response) { + // #swagger.tags = ['UI'] + // #swagger.summary = 'Check if a specific component is installed' + // #swagger.parameters['component'] = { description: 'Component to check' } + const component = req.params.component; + const result = await req.app.locals.settings.checkComponent(component); + res.send(result); +}); + Router.get('/cli/config/k8s/context', bearerMiddleware, async function (req: Request, res: Response) { // #swagger.tags = ['Config']