From ff3011d39c0500742f2cad1b1ee490e284590f10 Mon Sep 17 00:00:00 2001 From: ArnaudTa <33383276+ArnaudTA@users.noreply.github.com> Date: Wed, 29 Jan 2025 20:57:58 +0100 Subject: [PATCH 1/3] feat: support new naming system --- .github/workflows/release.yml | 2 +- files/logo.png | Bin 0 -> 6907 bytes package-lock.json | 346 +++++++++++++++------------------- package.json | 12 +- src/env.d.ts | 2 + src/function.ts | 69 ++++--- src/gitlab.ts | 1 - src/index.ts | 6 +- src/infos.ts | 44 ++++- src/utils.ts | 11 +- src/yaml.ts | 86 ++++----- 11 files changed, 293 insertions(+), 286 deletions(-) create mode 100644 files/logo.png diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6413c58..5e86552 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: run: rm -rf ./node_modules && npm install --ignore-scripts --omit=dev - name: Zip build folder - run: zip -r ./package.zip ./package.json ./dist ./node_modules + run: zip -r ./package.zip ./package.json ./dist ./node_modules ./files - name: Upload release artifact uses: actions/upload-release-asset@v1 diff --git a/files/logo.png b/files/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c085573b59c9972efc31b44ddcdc50d417bdc5c1 GIT binary patch literal 6907 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.0.0" + }, + "peerDependencies": { + "typescript": "3.x || 4.x || 5.x", + "vitest": ">=2.0.0" + } + }, + "node_modules/@cpn-console/hooks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@cpn-console/hooks/-/hooks-4.0.0.tgz", + "integrity": "sha512-/T2I2TtpuofqXOheh/uXXK8ivLbBHBsYQS18o2G/rhd80yzL0GGIXERjw1OElfEpqyQynTz5f4Y9iTXwybI6lw==", + "dependencies": { + "@cpn-console/shared": "^1.3.0", + "json-schema": "^0.4.0", + "vitest-mock-extended": "^2.0.2", + "zod": "^3.24.1" + } + }, "node_modules/@cpn-console/keycloak-plugin": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@cpn-console/keycloak-plugin/-/keycloak-plugin-2.0.6.tgz", - "integrity": "sha512-ofez61fJW8kV8NbflhcOBQ78wucmaty7D9r2AZMt/p4u3NcdDnJ3Qi9yflaD00p/0ui96YZDPoMlNtwQxZLctw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cpn-console/keycloak-plugin/-/keycloak-plugin-2.1.0.tgz", + "integrity": "sha512-ZME+tYZtnLVDLHHQdZNyIPsG/s7HQJdpSUWiTf0svgqmnI6RLfrwrKULabDU75VwJ3IqfPi8lbDulFzadU6Hrw==", "dev": true, "dependencies": { - "@cpn-console/hooks": "^2.5.0", - "@cpn-console/shared": "^1.2.0", + "@cpn-console/hooks": "^3.0.0", + "@cpn-console/shared": "^1.3.0", "@keycloak/keycloak-admin-client": "^26.0.7", "axios": "^1.7.9" } }, + "node_modules/@cpn-console/keycloak-plugin/node_modules/@cpn-console/hooks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@cpn-console/hooks/-/hooks-3.0.0.tgz", + "integrity": "sha512-D146UvkHGLhHEdCmszq00XXeJBfx8T9pZcoJLx2FiaZyyPuRCXMIN06z9x5NcXz/loWBTvl2gHWpwT9xQk0UjA==", + "dev": true, + "dependencies": { + "@cpn-console/shared": "^1.2.0", + "json-schema": "^0.4.0", + "vitest-mock-extended": "^2.0.2", + "zod": "^3.24.1" + } + }, "node_modules/@cpn-console/kubernetes-plugin": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@cpn-console/kubernetes-plugin/-/kubernetes-plugin-2.1.1.tgz", - "integrity": "sha512-BtAeUZgj0Q+G//QcJBSZ7mKOApLH0bLkm15WxCfoiMgWJwwPPSbQ6kTJRw3yMI1L0x30XBFEU+8F0FZo6tNVlQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@cpn-console/kubernetes-plugin/-/kubernetes-plugin-2.3.0.tgz", + "integrity": "sha512-0XyT8fGNpLrgR+7YsA3gw7Y3fYqHEHPo1xcgtKz7kh5BLZ0gG6iblh+Dc6Fu87ZWSd6T3fpxFy6qiLxXzk3hDQ==", "dev": true, "dependencies": { - "@cpn-console/hooks": "^2.5.0", + "@cpn-console/hooks": "^3.0.0", "@cpn-console/shared": "^1.2.0", "@kubernetes-models/argo-cd": "^2.6.2", "@kubernetes/client-node": "^0.22.3", @@ -332,6 +370,18 @@ "request": "^2.88.2" } }, + "node_modules/@cpn-console/kubernetes-plugin/node_modules/@cpn-console/hooks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@cpn-console/hooks/-/hooks-3.0.0.tgz", + "integrity": "sha512-D146UvkHGLhHEdCmszq00XXeJBfx8T9pZcoJLx2FiaZyyPuRCXMIN06z9x5NcXz/loWBTvl2gHWpwT9xQk0UjA==", + "dev": true, + "dependencies": { + "@cpn-console/shared": "^1.2.0", + "json-schema": "^0.4.0", + "vitest-mock-extended": "^2.0.2", + "zod": "^3.24.1" + } + }, "node_modules/@cpn-console/kubernetes-plugin/node_modules/@kubernetes/client-node": { "version": "0.22.3", "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.22.3.tgz", @@ -355,13 +405,14 @@ } }, "node_modules/@cpn-console/shared": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@cpn-console/shared/-/shared-1.2.0.tgz", - "integrity": "sha512-NvZwaumEMqiYvZrGOzElLdhyvnYgHGzfNqZsSduiKCgCpIXUAg9LagJwru/SXElYzqLGHjWg3+h3YHipVXPp1A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@cpn-console/shared/-/shared-1.3.0.tgz", + "integrity": "sha512-c6lhxGzEQ8Kdaw4LHCUU67Ww5z/HbC6oku3SIG9dOhI7dpbPWGFEClT3ZdkURf/EkfgrTPNEhJPyT98Ez2DhjA==", "dependencies": { - "@ts-rest/core": "^3.45.2", - "zod": "^3.23.8", - "zod-validation-error": "^3.3.0" + "@ts-rest/core": "^3.51.0", + "short-uuid": "^5.2.0", + "zod": "^3.24.1", + "zod-validation-error": "^3.4.0" } }, "node_modules/@cpn-console/ts-config": { @@ -371,124 +422,65 @@ "dev": true }, "node_modules/@cpn-console/vault-plugin": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@cpn-console/vault-plugin/-/vault-plugin-2.2.1.tgz", - "integrity": "sha512-klojPozsy/+R8rVSxuvoUU15M27dxqger7LJyukSkAqNyUsCrq2XxIKzuJGV1E95oOQi1SeqKU2V4ncPLAbKNA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@cpn-console/vault-plugin/-/vault-plugin-2.2.2.tgz", + "integrity": "sha512-6ZhzGgWWwH5W+TqlRG7f4eWr3DHKQH8TrPxdNZlmXI7suOcYOVY4dkZVtrtU1qKHI+0JBd1RY6FHtAWbPgZ0cg==", "dev": true, "dependencies": { "@cpn-console/hooks": "^2.5.0", "@cpn-console/shared": "^1.2.0", - "@kubernetes/client-node": "^0.21.0", - "axios": "^1.7.2" + "@kubernetes/client-node": "^0.22.3", + "axios": "^1.7.9" + } + }, + "node_modules/@cpn-console/vault-plugin/node_modules/@cpn-console/hooks": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cpn-console/hooks/-/hooks-2.5.0.tgz", + "integrity": "sha512-3SkGxPLEWX+7teNpN8G/m1wZA2zr80aJoTsFBtjQuS56ld1N9QW7y6TMtjdAlsu86bc3L7YMmbcZPnU+qDbpUw==", + "dev": true, + "dependencies": { + "@cpn-console/shared": "^1.2.0", + "json-schema": "^0.4.0", + "vitest-mock-extended": "^1.3.1", + "zod": "^3.23.8" } }, "node_modules/@cpn-console/vault-plugin/node_modules/@kubernetes/client-node": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.21.0.tgz", - "integrity": "sha512-yYRbgMeyQbvZDHt/ZqsW3m4lRefzhbbJEuj8sVXM+bufKrgmzriA2oq7lWPH/k/LQIicAME9ixPUadTrxIF6dQ==", + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.22.3.tgz", + "integrity": "sha512-dG8uah3+HDJLpJEESshLRZlAZ4PgDeV9mZXT0u1g7oy4KMRzdZ7n5g0JEIlL6QhK51/2ztcIqURAnjfjJt6Z+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@types/js-yaml": "^4.0.1", - "@types/node": "^20.1.1", - "@types/request": "^2.47.1", - "@types/ws": "^8.5.3", "byline": "^5.0.0", "isomorphic-ws": "^5.0.0", "js-yaml": "^4.1.0", - "jsonpath-plus": "^8.0.0", + "jsonpath-plus": "^10.2.0", "request": "^2.88.0", "rfc4648": "^1.3.0", "stream-buffers": "^3.0.2", "tar": "^7.0.0", "tslib": "^2.4.1", - "ws": "^8.11.0" + "ws": "^8.18.0" }, "optionalDependencies": { - "openid-client": "^5.3.0" - } - }, - "node_modules/@cpn-console/vault-plugin/node_modules/@types/node": { - "version": "20.17.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz", - "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@cpn-console/vault-plugin/node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "dev": true, - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/@cpn-console/vault-plugin/node_modules/jsonpath-plus": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-8.1.0.tgz", - "integrity": "sha512-qVTiuKztFGw0dGhYi3WNqvddx3/SHtyDT0xJaeyz4uP0d1tkpG+0y5uYQ4OcIo1TLAz3PE/qDOW9F0uDt3+CTw==", - "dev": true, - "license": "MIT", - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@cpn-console/vault-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "openid-client": "^6.1.3" } }, - "node_modules/@cpn-console/vault-plugin/node_modules/openid-client": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", - "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "node_modules/@cpn-console/vault-plugin/node_modules/vitest-mock-extended": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/vitest-mock-extended/-/vitest-mock-extended-1.3.2.tgz", + "integrity": "sha512-wnpym69MFYBUbUT6vrM/E4sF0bylow+N/RBBTZWn4rO/UFLusvuCrb3CMe3K4663+iBzplrhk0hQ2O246rFrqQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "jose": "^4.15.9", - "lru-cache": "^6.0.0", - "object-hash": "^2.2.0", - "oidc-token-hash": "^5.0.3" + "ts-essentials": ">=10.0.0" }, - "funding": { - "url": "https://github.com/sponsors/panva" + "peerDependencies": { + "typescript": "3.x || 4.x || 5.x", + "vitest": ">=2.0.0" } }, - "node_modules/@cpn-console/vault-plugin/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cpn-console/vault-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/@es-joy/jsdoccomment": { "version": "0.50.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.0.tgz", @@ -1275,13 +1267,6 @@ } } }, - "node_modules/@types/caseless": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", - "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -1384,35 +1369,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/request": { - "version": "2.48.12", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", - "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - } - }, - "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", - "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/@types/stream-buffers": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.7.tgz", @@ -1441,13 +1397,6 @@ "node": ">=8" } }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -1942,6 +1891,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -6060,17 +6015,6 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -6083,17 +6027,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": "^10.13.0 || >=12.0.0" - } - }, "node_modules/onetime": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", @@ -7008,6 +6941,32 @@ "node": ">=8" } }, + "node_modules/short-uuid": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.2.0.tgz", + "integrity": "sha512-296/Nzi4DmANh93iYBwT4NoYRJuHnKEzefrkSagQbTH/A6NTaB68hSPDjm5IlbI5dx9FXdmtqPcj6N5H+CPm6w==", + "license": "MIT", + "dependencies": { + "any-base": "^1.1.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/short-uuid/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -7603,9 +7562,10 @@ } }, "node_modules/ts-essentials": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.1.tgz", - "integrity": "sha512-HPH+H2bkkO8FkMDau+hFvv7KYozzned9Zr1Urn7rRPXMF4mZmCKOq+u4AI1AAW+2bofIOXTuSdKo9drQuni2dQ==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.4.tgz", + "integrity": "sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==", + "license": "MIT", "peerDependencies": { "typescript": ">=4.5.0" }, @@ -8025,9 +7985,10 @@ } }, "node_modules/vitest-mock-extended": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/vitest-mock-extended/-/vitest-mock-extended-1.3.2.tgz", - "integrity": "sha512-wnpym69MFYBUbUT6vrM/E4sF0bylow+N/RBBTZWn4rO/UFLusvuCrb3CMe3K4663+iBzplrhk0hQ2O246rFrqQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vitest-mock-extended/-/vitest-mock-extended-2.0.2.tgz", + "integrity": "sha512-n3MBqVITKyclZ0n0y66hkT4UiiEYFQn9tteAnIxT0MPz1Z8nFcPUG3Cf0cZOyoPOj/cq6Ab1XFw2lM/qM5EDWQ==", + "license": "MIT", "dependencies": { "ts-essentials": ">=10.0.0" }, @@ -8451,9 +8412,10 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index a2987e4..7bc5d5f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@cpn-console/observability-plugin", "type": "module", - "version": "0.1.2", + "version": "0.1.3", "description": "Loki plugin for DSO console", "exports": { ".": { @@ -26,7 +26,7 @@ "prepare": "husky" }, "dependencies": { - "@cpn-console/hooks": "^2.5.0", + "@cpn-console/hooks": "^4.0.0", "@gitbeaker/core": "~40.6.0", "@gitbeaker/requester-utils": "~40.6.0", "@gitbeaker/rest": "~40.6.0", @@ -39,12 +39,12 @@ "devDependencies": { "@antfu/eslint-config": "^3.16.0", "@cpn-console/eslint-config": "^1.0.2", - "@cpn-console/gitlab-plugin": "^3.0.0", - "@cpn-console/keycloak-plugin": "^2.0.6", - "@cpn-console/kubernetes-plugin": "^2.1.1", + "@cpn-console/gitlab-plugin": "^3.1.0", + "@cpn-console/keycloak-plugin": "^2.1.0", + "@cpn-console/kubernetes-plugin": "^2.3.0", "@cpn-console/shared": "^1.2.0", "@cpn-console/ts-config": "^1.1.0", - "@cpn-console/vault-plugin": "^2.2.1", + "@cpn-console/vault-plugin": "^2.2.2", "@types/js-yaml": "^4.0.9", "@types/node": "^22.10.7", "@types/uuid": "^10.0.0", diff --git a/src/env.d.ts b/src/env.d.ts index a2f8c1e..2ffe8e6 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -2,3 +2,5 @@ /// /// /// +/// +/// diff --git a/src/function.ts b/src/function.ts index e4ad5e5..c6e8c76 100644 --- a/src/function.ts +++ b/src/function.ts @@ -1,13 +1,19 @@ -import type { Environment, Project, StepCall, UserObject } from '@cpn-console/hooks' +import type { Environment, PluginResult, Project, StepCall, UserObject } from '@cpn-console/hooks' +import type { KeycloakProjectApi } from '@cpn-console/keycloak-plugin/types/class.js' import type { Gitlab as GitlabInterface } from '@gitbeaker/core' -import type { BaseParams, Stage } from './utils.js' -import { parseError } from '@cpn-console/hooks' -import { removeTrailingSlash, requiredEnv } from '@cpn-console/shared' +import { parseError, specificallyDisabled } from '@cpn-console/hooks' +import { compressUUID, removeTrailingSlash, requiredEnv } from '@cpn-console/shared' import { Gitlab } from '@gitbeaker/rest' import { deleteKeycloakGroup, ensureKeycloakGroups } from './keycloak.js' +import { isNewNsName, type TenantKeycloakMapper } from './utils.js' import { deleteGitlabYamlConfig, upsertGitlabConfig } from './yaml.js' -const getBaseParams = (project: Project, stage: Stage): BaseParams => ({ organizationName: project.organization.name, projectName: project.name, stage }) +const okSkipped: PluginResult = { + status: { + result: 'OK', + message: 'Plugin disabled', + }, +} export type ListPerms = Record<'prod' | 'hors-prod', Record<'view' | 'edit', UserObject['id'][]>> @@ -58,35 +64,44 @@ function getGitlabApi(): GitlabInterface { export const upsertProject: StepCall = async (payload) => { try { + if (specificallyDisabled(payload.config.observability?.enabled)) { + return okSkipped + } // init args const project = payload.args - const keycloakApi = payload.apis.keycloak - const vaultApi = payload.apis.vault + const keycloakApi = payload.apis.keycloak as KeycloakProjectApi // init gitlab api const gitlabApi = getGitlabApi() const keycloakRootGroupPath = await keycloakApi.getProjectGroupPath() + const tenantRbacProd = [`${keycloakRootGroupPath}/grafana/prod-RW`, `${keycloakRootGroupPath}/grafana/prod-RO`] + const tenantRbacHProd = [`${keycloakRootGroupPath}/grafana/hprod-RW`, `${keycloakRootGroupPath}/grafana/hprod-RO`] + + const compressedUUID = compressUUID(project.id) + + const tenantsToCreate: TenantKeycloakMapper = {} + + for (const environment of payload.args.environments) { + if (!environment.apis.kubernetes) { + throw new Error(`no kubernetes apis on environment ${environment.name}`) + } + const gen = isNewNsName(await environment.apis.kubernetes.getNsName()) ? compressedUUID : project.slug + if (environment.stage === 'prod') { + tenantsToCreate[`prod-${gen}`] = tenantRbacProd + } else { + tenantsToCreate[`hprod-${gen}`] = tenantRbacHProd + } + } - const hasProd = project.environments.find(env => env.stage === 'prod') - const hasNonProd = project.environments.find(env => env.stage !== 'prod') - const hProdParams = getBaseParams(project, 'hprod') - const prodParams = getBaseParams(project, 'prod') const listPerms = getListPrems(project.environments) - await Promise.all([ - ensureKeycloakGroups(listPerms, keycloakApi), - // Upsert or delete Gitlab config based on prod/non-prod environment - ...(hasProd - ? [await upsertGitlabConfig(prodParams, keycloakRootGroupPath, project, gitlabApi, vaultApi)] - : [await deleteGitlabYamlConfig(prodParams, project, gitlabApi)]), - ...(hasNonProd - ? [await upsertGitlabConfig(hProdParams, keycloakRootGroupPath, project, gitlabApi, vaultApi)] - : [await deleteGitlabYamlConfig(hProdParams, project, gitlabApi)]), - ]) + // Upsert or delete Gitlab config based on prod/non-prod environment + const yamlResult = await upsertGitlabConfig(project, gitlabApi, tenantsToCreate) + await ensureKeycloakGroups(listPerms, keycloakApi) return { status: { result: 'OK', - message: 'Created', + message: yamlResult, }, } } catch (error) { @@ -102,16 +117,16 @@ export const upsertProject: StepCall = async (payload) => { export const deleteProject: StepCall = async (payload) => { try { + if (specificallyDisabled(payload.config.observability?.enabled)) { + return okSkipped + } const project = payload.args const gitlabApi = getGitlabApi() - const keycloakApi = payload.apis.keycloak - const hProdParams = getBaseParams(project, 'hprod') - const prodParams = getBaseParams(project, 'prod') + const keycloakApi = payload.apis.keycloak as KeycloakProjectApi await Promise.all([ deleteKeycloakGroup(keycloakApi), - deleteGitlabYamlConfig(prodParams, project, gitlabApi), - deleteGitlabYamlConfig(hProdParams, project, gitlabApi), + deleteGitlabYamlConfig(project, gitlabApi), ]) return { diff --git a/src/gitlab.ts b/src/gitlab.ts index d7cd501..b02502b 100644 --- a/src/gitlab.ts +++ b/src/gitlab.ts @@ -55,7 +55,6 @@ export async function getGitlabYamlFileContent(api: IGitlab, project: Project, f // Fonction pour éditer, committer et pousser un fichier YAML export async function commitAndPushYamlFile(api: IGitlab, project: Project, filePath: string, branch: string, commitMessage: string, yamlString: string): Promise { - console.log('yamlString: ', yamlString) const encodedContent = Buffer.from(yamlString).toString('utf-8') try { // Vérifier si le fichier existe déjà diff --git a/src/index.ts b/src/index.ts index 82c2785..43ecc41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import type { Plugin } from '@cpn-console/hooks' +import type { DeclareModuleGenerator, Plugin } from '@cpn-console/hooks' import { requiredEnv } from '@cpn-console/shared' import { deleteProject, upsertProject } from './function.js' import infos from './infos.js' @@ -19,3 +19,7 @@ export const plugin: Plugin = { }, start: () => { requiredEnv('GRAFANA_URL') }, // to check is the variable is set, unless it crashes the app } + +declare module '@cpn-console/hooks' { + interface Config extends DeclareModuleGenerator {} +} diff --git a/src/infos.ts b/src/infos.ts index d7f07bb..32ccdff 100644 --- a/src/infos.ts +++ b/src/infos.ts @@ -1,21 +1,49 @@ import type { ServiceInfos } from '@cpn-console/hooks' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { compressUUID, ENABLED } from '@cpn-console/shared' import { getConfig } from './utils.js' -const infos: ServiceInfos = { +const imageData = Buffer.from((readFileSync(join(import.meta.dirname, '../files/logo.png'))).toString('base64')) + +const infos = { name: 'observability', - to: ({ project, organization }) => [ + to: ({ project }) => [ + { + to: `${getConfig().grafanaUrl}/prod-${compressUUID(project.id)}`, + description: 'Production', + }, + { + to: `${getConfig().grafanaUrl}/prod-${project.slug}`, + description: 'Production ancien', + }, { - to: `${getConfig().grafanaUrl}/hprod-${organization}-${project}`, - title: 'Hors production', + to: `${getConfig().grafanaUrl}/hprod-${compressUUID(project.id)}`, + description: 'Hors production', }, { - to: `${getConfig().grafanaUrl}/prod-${organization}-${project}`, - title: 'Production', + to: `${getConfig().grafanaUrl}/hprod-${project.slug}`, + description: 'Hors production ancien', }, ], title: 'Grafana', - imgSrc: 'https://upload.wikimedia.org/wikipedia/commons/a/a1/Grafana_logo.svg', + imgSrc: `data:image/png;base64,${imageData}`, description: 'Grafana est un outil de métrique et de logs', -} + config: { + global: [{ + kind: 'switch', + key: 'enabled', + initialValue: ENABLED, + permissions: { + admin: { read: true, write: true }, + user: { read: true, write: false }, + }, + title: 'Activer le plugin', + value: ENABLED, + description: 'Activer le plugin', + }], + project: [], + }, +} as const satisfies ServiceInfos export default infos diff --git a/src/utils.ts b/src/utils.ts index 2e8ebc8..9347ba5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -62,8 +62,11 @@ export function getCustomK8sApi(): CustomObjectsApi { export type Stage = 'prod' | 'hprod' -export interface BaseParams { - organizationName: string - projectName: string - stage: Stage +export interface TenantKeycloakMapper { + [x: string]: string[] +} + +const re = /[a-z0-9]{25}--[a-z0-9]{25}/ +export function isNewNsName(ns: string) { + return re.test(ns) } diff --git a/src/yaml.ts b/src/yaml.ts index bbf2e52..123601d 100644 --- a/src/yaml.ts +++ b/src/yaml.ts @@ -1,11 +1,10 @@ import type { Project } from '@cpn-console/hooks' -import type { VaultProjectApi } from '@cpn-console/vault-plugin/types/class.js' import type { Gitlab as GitlabInterface } from '@gitbeaker/core' import type { Project as GitlabProject, Group, } from './gitlab.js' -import type { BaseParams } from './utils.js' +import type { TenantKeycloakMapper } from './utils.js' // @ts-ignore import yaml from 'js-yaml' import { @@ -98,7 +97,7 @@ async function findOrCreateValuesFile(gitlabApi: GitlabInterface, project: Gitla } } -export async function upsertGitlabConfig(params: BaseParams, keycloakRootGroupPath: string, project: Project, gitlabApi: GitlabInterface, _vaultApi: VaultProjectApi) { +export async function upsertGitlabConfig(project: Project, gitlabApi: GitlabInterface, tenants: TenantKeycloakMapper) { // Déplacer toute la logique de création ou de récupération de groupe et de repo ici const lokiGroupName = 'observability' const lokiRepoName = 'observability' @@ -109,35 +108,46 @@ export async function upsertGitlabConfig(params: BaseParams, keycloakRootGroupPa const file = await findOrCreateValuesFile(gitlabApi, gitlabLokiRepo) let yamlFile = await readYamlFile(Buffer.from(file, 'utf-8').toString('utf-8')) - const tenantName = `${params.stage}-${params.organizationName}-${params.projectName}` - const tenantRbac = [`${keycloakRootGroupPath}/grafana/${params.stage}-RW`, `${keycloakRootGroupPath}/grafana/${params.stage}-RO`] - - // const infraReposUrls: string[] = [] - // for (const repo of project.repositories) { - // if (repo.isInfra) { - // const repoInternalUrl = (await vaultApi.read(`${params.organizationName}/${params.projectName}/${repo.internalRepoName}-mirror`)).data.GIT_OUTPUT_URL as string - // if (repoInternalUrl) { - // infraReposUrls.push(`https://${repoInternalUrl}`) - // } - // } - // } - - const projectData: ProjectLoki = { - name: tenantName, - groups: tenantRbac, + let needUpdates = false + + const shouldBeRemoved: string[] = [] + let notFoundTenants: string[] = Object.keys(tenants) + + for (const tenant of yamlFile.global.tenants) { + if (tenant.uuid !== project.id) continue + if (tenant.name in tenants) { + if (tenant.groups.toString() !== tenants[tenant.name].toString()) { + needUpdates = true + tenant.groups = structuredClone(tenants[tenant.name]) + } + notFoundTenants = notFoundTenants.filter(notFoundTenant => notFoundTenant !== tenant.name) + } else { + needUpdates = true + shouldBeRemoved.push(tenant.name) + } + } + + const newTenants = notFoundTenants.map((notFoundTenant): ProjectLoki => ({ + groups: structuredClone(tenants[notFoundTenant]), + name: notFoundTenant, uuid: project.id, - // urls: infraReposUrls, + })) + + yamlFile = { + ...yamlFile, + global: { + ...yamlFile.global, + tenants: [...yamlFile.global.tenants.filter(tenant => tenant.uuid !== project.id || !shouldBeRemoved.includes(tenant.name)), ...newTenants], + }, } - if (findTenantByName(yamlFile, tenantName)) { - return + if (!needUpdates && !newTenants.length) { + return 'Already up-to-date' } - // Modifier le fichier YAML et commiter - yamlFile = addYamlObjectToRepo(yamlFile, projectData) const yamlString = writeYamlFile(yamlFile) - return commitAndPushYamlFile( + await commitAndPushYamlFile( gitlabApi, gitlabLokiRepo, valuesPath, @@ -145,9 +155,10 @@ export async function upsertGitlabConfig(params: BaseParams, keycloakRootGroupPa `Add project ${project.name}`, yamlString, ) + return `created: ${newTenants.map(tenant => tenant.name)}, deleted: ${shouldBeRemoved}` } -export async function deleteGitlabYamlConfig(params: BaseParams, project: Project, gitlabApi: GitlabInterface) { +export async function deleteGitlabYamlConfig(project: Project, gitlabApi: GitlabInterface) { // Même logique de groupe et de repo que pour l'upsert const lokiGroupName = 'observability' const lokiRepoName = 'observability' @@ -158,16 +169,13 @@ export async function deleteGitlabYamlConfig(params: BaseParams, project: Projec const file = await findOrCreateValuesFile(gitlabApi, gitlabLokiRepo) let yamlFile = await readYamlFile(Buffer.from(file, 'utf-8').toString('utf-8')) - const tenantName = `${params.stage}-${params.organizationName}-${params.projectName}` - // Rechercher le projet à supprimer - const projectToDelete = yamlFile.global.tenants.find(tenant => tenant.name === tenantName) - if (!projectToDelete) { + if (!yamlFile.global.tenants.find(tenant => tenant.uuid === project.id)) { return } // Modifier le fichier YAML et commiter - yamlFile = removeRepo(yamlFile, projectToDelete.uuid) + yamlFile = removeProject(yamlFile, project.id) const yamlString = writeYamlFile(yamlFile) return commitAndPushYamlFile( @@ -180,21 +188,7 @@ export async function deleteGitlabYamlConfig(params: BaseParams, project: Projec ) } -function addYamlObjectToRepo(data: YamlLokiData, newProject: ProjectLoki): YamlLokiData { - return { - ...data, - global: { - ...data.global, - tenants: [...data.global.tenants, newProject], - }, - } -} - -function findTenantByName(data: YamlLokiData, name: string): ProjectLoki | undefined { - return data.global.tenants.find(tenant => tenant.name === name) -} - -function removeRepo(data: YamlLokiData, uuid: string): YamlLokiData { +function removeProject(data: YamlLokiData, uuid: string): YamlLokiData { return { ...data, global: { From 54a40fb0dbb5c4e20585ba95f667b7469c201542 Mon Sep 17 00:00:00 2001 From: ArnaudTa <33383276+ArnaudTA@users.noreply.github.com> Date: Thu, 6 Feb 2025 22:48:21 +0100 Subject: [PATCH 2/3] fix: :bug: add retro compatibility for 8.24.2 --- files/logo.png | Bin 6907 -> 8091 bytes package.json | 2 +- src/function.ts | 14 +++++++++--- src/infos.ts | 57 +++++++++++++++++++++++++++++++++--------------- src/utils.ts | 7 +++++- src/yaml.ts | 34 +++++++++++++++++++++-------- 6 files changed, 82 insertions(+), 32 deletions(-) diff --git a/files/logo.png b/files/logo.png index c085573b59c9972efc31b44ddcdc50d417bdc5c1..2ac9ef9ab75ac94e93141336e948f94c085c35f2 100644 GIT binary patch literal 8091 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 literal 6907 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 = async (payload) => { if (!environment.apis.kubernetes) { throw new Error(`no kubernetes apis on environment ${environment.name}`) } - const gen = isNewNsName(await environment.apis.kubernetes.getNsName()) ? compressedUUID : project.slug + const name = isNewNsName(await environment.apis.kubernetes.getNsName()) ? compressedUUID : project.slug if (environment.stage === 'prod') { - tenantsToCreate[`prod-${gen}`] = tenantRbacProd + tenantsToCreate[`prod-${name}`] = { + groups: tenantRbacProd, + name, + type: 'prod', + } } else { - tenantsToCreate[`hprod-${gen}`] = tenantRbacHProd + tenantsToCreate[`hprod-${name}`] = { + groups: tenantRbacHProd, + name, + type: 'hprod', + } } } diff --git a/src/infos.ts b/src/infos.ts index 32ccdff..4edb87e 100644 --- a/src/infos.ts +++ b/src/infos.ts @@ -8,24 +8,45 @@ const imageData = Buffer.from((readFileSync(join(import.meta.dirname, '../files/ const infos = { name: 'observability', - to: ({ project }) => [ - { - to: `${getConfig().grafanaUrl}/prod-${compressUUID(project.id)}`, - description: 'Production', - }, - { - to: `${getConfig().grafanaUrl}/prod-${project.slug}`, - description: 'Production ancien', - }, - { - to: `${getConfig().grafanaUrl}/hprod-${compressUUID(project.id)}`, - description: 'Hors production', - }, - { - to: `${getConfig().grafanaUrl}/hprod-${project.slug}`, - description: 'Hors production ancien', - }, - ], + // @ts-ignore retro compatibility + to: ({ project, projectId, organization }) => { + let isInfV9 = false + const params = { + id: '', + slug: '', + } + const grafanaUrl = getConfig().grafanaUrl + if (typeof project === 'string' && typeof organization === 'string') { + params.id = projectId + params.slug = `${organization}-${project}` + isInfV9 = true + } else { + params.id = project.id + params.slug = project.slug + } + return [ + { + to: `${grafanaUrl}/prod-${compressUUID(String(params.id))}`, + title: isInfV9 ? 'Production' : undefined, + description: 'Production', + }, + { + to: `${grafanaUrl}/prod-${params.slug}`, + title: isInfV9 ? 'Production ancien' : undefined, + description: 'Production ancien', + }, + { + to: `${grafanaUrl}/hprod-${compressUUID(String(params.id))}`, + title: isInfV9 ? 'Hors production' : undefined, + description: 'Hors production', + }, + { + to: `${grafanaUrl}/hprod-${params.slug}`, + title: isInfV9 ? 'Hors production ancien' : undefined, + description: 'Hors production ancien', + }, + ] + }, title: 'Grafana', imgSrc: `data:image/png;base64,${imageData}`, description: 'Grafana est un outil de métrique et de logs', diff --git a/src/utils.ts b/src/utils.ts index 9347ba5..28c829c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -62,8 +62,13 @@ export function getCustomK8sApi(): CustomObjectsApi { export type Stage = 'prod' | 'hprod' +export interface TenantInfo { + groups: string[] + type: 'prod' | 'hprod' + name: string // tenant name, short-uuid or slug +} export interface TenantKeycloakMapper { - [x: string]: string[] + [x: string]: TenantInfo // fullName, type + (short-uuid or slug) } const re = /[a-z0-9]{25}--[a-z0-9]{25}/ diff --git a/src/yaml.ts b/src/yaml.ts index 123601d..aaf8525 100644 --- a/src/yaml.ts +++ b/src/yaml.ts @@ -4,7 +4,7 @@ import type { Project as GitlabProject, Group, } from './gitlab.js' -import type { TenantKeycloakMapper } from './utils.js' +import type { TenantInfo, TenantKeycloakMapper } from './utils.js' // @ts-ignore import yaml from 'js-yaml' import { @@ -20,9 +20,11 @@ const valuesPath = 'helm/values.yaml' const valuesBranch = 'main' interface ProjectLoki { - name: string + project: string // slug + name: string // slug ou short-uuid (capture regex) groups: string[] - uuid: string + uuid: string // uuid du projet + type: 'prod' | 'hprod' // urls: string[] } @@ -115,12 +117,26 @@ export async function upsertGitlabConfig(project: Project, gitlabApi: GitlabInte for (const tenant of yamlFile.global.tenants) { if (tenant.uuid !== project.id) continue - if (tenant.name in tenants) { - if (tenant.groups.toString() !== tenants[tenant.name].toString()) { + const fullName = `${tenant.type}-${tenant.name}` + const matchingTenant = tenants[fullName] as TenantInfo | undefined + if (matchingTenant) { + if (tenant.groups.toString() !== tenants[tenant.name].groups.toString()) { needUpdates = true - tenant.groups = structuredClone(tenants[tenant.name]) + tenant.groups = structuredClone(tenants[tenant.name].groups) } - notFoundTenants = notFoundTenants.filter(notFoundTenant => notFoundTenant !== tenant.name) + if (tenant.project !== project.slug) { + needUpdates = true + tenant.project = project.slug + } + if (tenant.name !== matchingTenant.name) { + needUpdates = true + tenant.name = matchingTenant.name + } + if (tenant.type !== matchingTenant.type) { + needUpdates = true + tenant.type = matchingTenant.type + } + notFoundTenants = notFoundTenants.filter(notFoundTenant => notFoundTenant !== fullName) } else { needUpdates = true shouldBeRemoved.push(tenant.name) @@ -128,9 +144,9 @@ export async function upsertGitlabConfig(project: Project, gitlabApi: GitlabInte } const newTenants = notFoundTenants.map((notFoundTenant): ProjectLoki => ({ - groups: structuredClone(tenants[notFoundTenant]), - name: notFoundTenant, + ...structuredClone(tenants[notFoundTenant]), uuid: project.id, + project: project.slug, })) yamlFile = { From 887133aab21f97f9990bf576c6159f8a7bb5a695 Mon Sep 17 00:00:00 2001 From: ArnaudTa <33383276+ArnaudTA@users.noreply.github.com> Date: Tue, 11 Feb 2025 14:44:57 +0100 Subject: [PATCH 3/3] feat: :sparkles: new values schema It breaks everything --- .vscode/extensions.json | 28 ++++++++ .vscode/launch.json | 22 +++++++ .vscode/settings.json | 120 +++++++++++++++++++++++++++++++++++ package.json | 2 +- src/function.ts | 51 +++++++++------ src/index.ts | 1 + src/infos.ts | 51 ++++++++------- src/yaml.ts | 137 +++++++++++++++++++--------------------- 8 files changed, 298 insertions(+), 114 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4775b82 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,28 @@ +{ + "recommendations": [ + // Cross-editor config + "editorconfig.editorconfig", + // Git + "mhutchie.git-graph", + "eamodio.gitlens", + // Node / npm + "christian-kohler.npm-intellisense", + // JavaScript + "dbaeumer.vscode-eslint", + // Vue.js + "vue.volar", + "vue.vscode-typescript-vue-plugin", + // Vitejs + "antfu.vite", + "antfu.browse-lite", + // CSS + "csstools.postcss", + "stylelint.vscode-stylelint", + // YAML + "redhat.vscode-yaml", + // Makdown + "yzhang.markdown-all-in-one", + // Kubernetes + "ms-kubernetes-tools.vscode-kubernetes-tools" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..55297a8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Remote", + "type": "node", + "request": "attach", + "port": 9229, + "address": "127.0.0.1", + "restart": true, + "localRoot": "${workspaceFolder}/apps/server", + "remoteRoot": "/app/apps/server", + "skipFiles": [ + "/**", + "${workspaceFolder}/node_modules/**/*.js" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5e7fe75 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,120 @@ +{ + // visuals + "window.autoDetectColorScheme": true, + "editor.fontLigatures": "'ss01', 'ss02', 'ss03', 'ss06', 'zero'", + "editor.cursorBlinking": "phase", + "editor.suggestSelection": "first", + "editor.wordWrap": "on", + "editor.bracketPairColorization.enabled": true, + "editor.tabSize": 2, + "search.exclude": { + "**/.git": true, + "**/.nuxt": true, + "**/.pnpm": true, + "**/.yarn": true, + "**/.turbo": true, + "**/dist/**": true, + "**/out/**": true, + "**/logs": true, + "**/node_modules": true, + "**/package-lock.json": true, + "**/pnpm-lock.yaml": true, + "**/yarn.lock": true + }, + // general + "editor.unicodeHighlight.invisibleCharacters": false, + "workbench.startupEditor": "none", + // git + "git.autofetch": true, + "git.untrackedChanges": "separate", + // github + "githubPullRequests.pullBranch": "never", + // eslint + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "vue", + "html", + "markdown", + "json", + "jsonc", + "yaml", + "github-actions-workflow", + "toml", + "xml", + "gql", + "graphql", + "astro", + "svelte", + "css", + "less", + "scss", + "pcss", + "postcss" + ], + // stylelint + "css.validate": true, + "less.validate": true, + "scss.validate": true, + "files.associations": { + "*.css": "css" + }, + // languages + "[javascript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "javascript.preferences.quoteStyle": "single", + "javascript.preferences.importModuleSpecifierEnding": "js", + "javascript.suggest.completeJSDocs": true, + "javascript.suggest.jsdoc.generateReturns": true, + "[typescript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "typescript.preferences.quoteStyle": "single", + "typescript.preferences.importModuleSpecifierEnding": "js", + "typescript.suggest.completeJSDocs": true, + "typescript.suggest.jsdoc.generateReturns": true, + "[vue]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "[css]": { + "editor.defaultFormatter": "stylelint.vscode-stylelint" + }, + "[html]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "[markdown]": { + "editor.quickSuggestions": { + "other": true, + "comments": true, + "strings": true + }, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.defaultFormatter": "yzhang.markdown-all-in-one" + }, + // conventional commits + "conventionalCommits.showNewVersionNotes": false, + // kubernetes + "vs-kubernetes": { + "vs-kubernetes.crd-code-completion": "enabled" + }, + "vscode-kubernetes.log-viewer.follow": true, + "vscode-kubernetes.log-viewer.timestamp": false, + "vscode-kubernetes.log-viewer.since": -1, + "vscode-kubernetes.log-viewer.tail": -1, + "vscode-kubernetes.log-viewer.destination": "Webview", + "vscode-kubernetes.log-viewer.wrap": true, + // sonarqube + "sonarlint.connectedMode.project": { + "connectionId": "https://sonarqube.fabrique-numerique.fr", + "projectKey": "cloud-pi-native_console_AYsa46O31ebDtzs-pYyZ" + }, + "prisma-smart-formatter.typescript.defaultFormatter": "dbaeumer.vscode-eslint", + "prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma" +} diff --git a/package.json b/package.json index 8941a20..bc8ca7a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@cpn-console/observability-plugin", "type": "module", - "version": "0.1.2-patch-1", + "version": "1.0.0", "description": "Loki plugin for DSO console", "exports": { ".": { diff --git a/src/function.ts b/src/function.ts index bc38731..40bab4b 100644 --- a/src/function.ts +++ b/src/function.ts @@ -5,8 +5,8 @@ import { parseError, specificallyDisabled } from '@cpn-console/hooks' import { compressUUID, removeTrailingSlash, requiredEnv } from '@cpn-console/shared' import { Gitlab } from '@gitbeaker/rest' import { deleteKeycloakGroup, ensureKeycloakGroups } from './keycloak.js' -import { isNewNsName, type TenantKeycloakMapper } from './utils.js' -import { deleteGitlabYamlConfig, upsertGitlabConfig } from './yaml.js' +import { isNewNsName } from './utils.js' +import { deleteGitlabYamlConfig, type ProjectLoki, type Type, upsertGitlabConfig } from './yaml.js' const okSkipped: PluginResult = { status: { @@ -78,32 +78,44 @@ export const upsertProject: StepCall = async (payload) => { const compressedUUID = compressUUID(project.id) - const tenantsToCreate: TenantKeycloakMapper = {} + const projectValue: ProjectLoki = { + projectName: project.slug, + envs: { + hprod: { + groups: tenantRbacHProd, + tenants: {}, + }, + prod: { + groups: tenantRbacProd, + tenants: {}, + }, + }, + } for (const environment of payload.args.environments) { if (!environment.apis.kubernetes) { throw new Error(`no kubernetes apis on environment ${environment.name}`) } - const name = isNewNsName(await environment.apis.kubernetes.getNsName()) ? compressedUUID : project.slug - if (environment.stage === 'prod') { - tenantsToCreate[`prod-${name}`] = { - groups: tenantRbacProd, - name, - type: 'prod', - } - } else { - tenantsToCreate[`hprod-${name}`] = { - groups: tenantRbacHProd, - name, - type: 'hprod', - } - } + const namespace = await environment.apis.kubernetes.getNsName() + const name = isNewNsName(namespace) ? compressedUUID : project.slug + console.log({ namespace, name }) + const env: Type = environment.stage === 'prod' ? 'prod' : 'hprod' + projectValue.envs[env].tenants[`${env}-${name}`] = {} + } + + if (projectValue.envs.hprod && !Object.values(projectValue.envs.hprod?.tenants).length) { + // @ts-ignore + delete projectValue.envs.hprod + } + if (projectValue.envs.prod && !Object.values(projectValue.envs.prod?.tenants).length) { + // @ts-ignore + delete projectValue.envs.prod } const listPerms = getListPrems(project.environments) // Upsert or delete Gitlab config based on prod/non-prod environment - const yamlResult = await upsertGitlabConfig(project, gitlabApi, tenantsToCreate) + const yamlResult = await upsertGitlabConfig(project, gitlabApi, projectValue) await ensureKeycloakGroups(listPerms, keycloakApi) return { @@ -111,6 +123,9 @@ export const upsertProject: StepCall = async (payload) => { result: 'OK', message: yamlResult, }, + store: { + instances: Object.keys(projectValue.envs).join(','), + }, } } catch (error) { return { diff --git a/src/index.ts b/src/index.ts index 43ecc41..3a9d14e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,4 +22,5 @@ export const plugin: Plugin = { declare module '@cpn-console/hooks' { interface Config extends DeclareModuleGenerator {} + interface ProjectStore extends DeclareModuleGenerator {} } diff --git a/src/infos.ts b/src/infos.ts index 4edb87e..cc0b108 100644 --- a/src/infos.ts +++ b/src/infos.ts @@ -1,7 +1,7 @@ import type { ServiceInfos } from '@cpn-console/hooks' import { readFileSync } from 'node:fs' import { join } from 'node:path' -import { compressUUID, ENABLED } from '@cpn-console/shared' +import { ENABLED } from '@cpn-console/shared' import { getConfig } from './utils.js' const imageData = Buffer.from((readFileSync(join(import.meta.dirname, '../files/logo.png'))).toString('base64')) @@ -9,7 +9,7 @@ const imageData = Buffer.from((readFileSync(join(import.meta.dirname, '../files/ const infos = { name: 'observability', // @ts-ignore retro compatibility - to: ({ project, projectId, organization }) => { + to: ({ project, projectId, organization, store }) => { let isInfV9 = false const params = { id: '', @@ -24,28 +24,23 @@ const infos = { params.id = project.id params.slug = project.slug } - return [ - { - to: `${grafanaUrl}/prod-${compressUUID(String(params.id))}`, - title: isInfV9 ? 'Production' : undefined, - description: 'Production', - }, - { - to: `${grafanaUrl}/prod-${params.slug}`, - title: isInfV9 ? 'Production ancien' : undefined, - description: 'Production ancien', - }, - { - to: `${grafanaUrl}/hprod-${compressUUID(String(params.id))}`, + const urls: Array<{ to: string, title?: string, description: string }> = [] + const instances = store.observability?.instances?.split(',') ?? [] + if (instances.includes('hprod')) { + urls.push({ + to: `${grafanaUrl}/hprod-${params.slug}`, title: isInfV9 ? 'Hors production' : undefined, description: 'Hors production', - }, - { - to: `${grafanaUrl}/hprod-${params.slug}`, - title: isInfV9 ? 'Hors production ancien' : undefined, - description: 'Hors production ancien', - }, - ] + }) + } + if (instances.includes('prod')) { + urls.push({ + to: `${grafanaUrl}/prod-${params.slug}`, + title: isInfV9 ? 'Production' : undefined, + description: 'Production', + }) + } + return urls }, title: 'Grafana', imgSrc: `data:image/png;base64,${imageData}`, @@ -63,7 +58,17 @@ const infos = { value: ENABLED, description: 'Activer le plugin', }], - project: [], + project: [{ + kind: 'text', + key: 'instances', + permissions: { + admin: { read: false, write: false }, + user: { read: false, write: false }, + }, + title: 'Instances actives', + value: '', + description: '', + }], }, } as const satisfies ServiceInfos diff --git a/src/yaml.ts b/src/yaml.ts index aaf8525..f8f8526 100644 --- a/src/yaml.ts +++ b/src/yaml.ts @@ -4,7 +4,6 @@ import type { Project as GitlabProject, Group, } from './gitlab.js' -import type { TenantInfo, TenantKeycloakMapper } from './utils.js' // @ts-ignore import yaml from 'js-yaml' import { @@ -19,18 +18,29 @@ import { const valuesPath = 'helm/values.yaml' const valuesBranch = 'main' -interface ProjectLoki { - project: string // slug - name: string // slug ou short-uuid (capture regex) - groups: string[] - uuid: string // uuid du projet - type: 'prod' | 'hprod' +export type Type = 'prod' | 'hprod' +interface Tenant {} + +interface Env { + groups?: string[] + tenants: { + [x: `${Type}-${string}`]: Tenant + } +} +export interface ProjectLoki { + projectName: string // slug + envs: { + prod: Env + hprod: Env + } // urls: string[] } interface YamlLokiData { - global: { - tenants: ProjectLoki[] + global?: { + projects?: { + [x: string]: ProjectLoki + } } } @@ -99,7 +109,7 @@ async function findOrCreateValuesFile(gitlabApi: GitlabInterface, project: Gitla } } -export async function upsertGitlabConfig(project: Project, gitlabApi: GitlabInterface, tenants: TenantKeycloakMapper) { +export async function upsertGitlabConfig(project: Project, gitlabApi: GitlabInterface, projectValue: ProjectLoki) { // Déplacer toute la logique de création ou de récupération de groupe et de repo ici const lokiGroupName = 'observability' const lokiRepoName = 'observability' @@ -108,70 +118,27 @@ export async function upsertGitlabConfig(project: Project, gitlabApi: GitlabInte // Récupérer ou créer le fichier values.yaml const file = await findOrCreateValuesFile(gitlabApi, gitlabLokiRepo) - let yamlFile = await readYamlFile(Buffer.from(file, 'utf-8').toString('utf-8')) - - let needUpdates = false - - const shouldBeRemoved: string[] = [] - let notFoundTenants: string[] = Object.keys(tenants) - - for (const tenant of yamlFile.global.tenants) { - if (tenant.uuid !== project.id) continue - const fullName = `${tenant.type}-${tenant.name}` - const matchingTenant = tenants[fullName] as TenantInfo | undefined - if (matchingTenant) { - if (tenant.groups.toString() !== tenants[tenant.name].groups.toString()) { - needUpdates = true - tenant.groups = structuredClone(tenants[tenant.name].groups) - } - if (tenant.project !== project.slug) { - needUpdates = true - tenant.project = project.slug - } - if (tenant.name !== matchingTenant.name) { - needUpdates = true - tenant.name = matchingTenant.name - } - if (tenant.type !== matchingTenant.type) { - needUpdates = true - tenant.type = matchingTenant.type - } - notFoundTenants = notFoundTenants.filter(notFoundTenant => notFoundTenant !== fullName) - } else { - needUpdates = true - shouldBeRemoved.push(tenant.name) - } - } - - const newTenants = notFoundTenants.map((notFoundTenant): ProjectLoki => ({ - ...structuredClone(tenants[notFoundTenant]), - uuid: project.id, - project: project.slug, - })) + const yamlFile = await readYamlFile(Buffer.from(file, 'utf-8').toString('utf-8')) - yamlFile = { + const projects = yamlFile.global?.projects || {} + projects[project.id] = projectValue + const yamlString = writeYamlFile({ ...yamlFile, global: { ...yamlFile.global, - tenants: [...yamlFile.global.tenants.filter(tenant => tenant.uuid !== project.id || !shouldBeRemoved.includes(tenant.name)), ...newTenants], + projects, }, - } - - if (!needUpdates && !newTenants.length) { - return 'Already up-to-date' - } - - const yamlString = writeYamlFile(yamlFile) + }) await commitAndPushYamlFile( gitlabApi, gitlabLokiRepo, valuesPath, valuesBranch, - `Add project ${project.name}`, + `Update project ${project.slug}`, yamlString, ) - return `created: ${newTenants.map(tenant => tenant.name)}, deleted: ${shouldBeRemoved}` + return `Update: ${project.slug}` } export async function deleteGitlabYamlConfig(project: Project, gitlabApi: GitlabInterface) { @@ -183,16 +150,16 @@ export async function deleteGitlabYamlConfig(project: Project, gitlabApi: Gitlab // Récupérer le fichier values.yaml const file = await findOrCreateValuesFile(gitlabApi, gitlabLokiRepo) - let yamlFile = await readYamlFile(Buffer.from(file, 'utf-8').toString('utf-8')) + const yamlFile = await readYamlFile(Buffer.from(file, 'utf-8').toString('utf-8')) // Rechercher le projet à supprimer - if (!yamlFile.global.tenants.find(tenant => tenant.uuid === project.id)) { + if (yamlFile.global?.projects && !(project.id in yamlFile.global.projects)) { return } // Modifier le fichier YAML et commiter - yamlFile = removeProject(yamlFile, project.id) - const yamlString = writeYamlFile(yamlFile) + const yamlFileStripped = removeProject(yamlFile, project.id) + const yamlString = writeYamlFile(yamlFileStripped) return commitAndPushYamlFile( gitlabApi, @@ -205,11 +172,37 @@ export async function deleteGitlabYamlConfig(project: Project, gitlabApi: Gitlab } function removeProject(data: YamlLokiData, uuid: string): YamlLokiData { - return { - ...data, - global: { - ...data.global, - tenants: data.global.tenants.filter(tenant => tenant.uuid !== uuid), - }, - } + const strippedData = structuredClone(data) + delete strippedData.global?.projects?.[uuid] + return strippedData } + +// function _doesValuesDiff(actuaValues: ProjectLoki, expectedValue: ProjectLoki): boolean { +// if (actuaValues.projectName !== expectedValue.projectName) +// return true + +// const actualEnvKeys = Object.entries(actuaValues.envs) as [['prod' | 'hprod', Env] ] +// if (actualEnvKeys.length !== Object.keys(expectedValue.envs).length) +// return true + +// for (const [envName, envValue] of actualEnvKeys) { +// if (!(envName in expectedValue.envs)) +// return true + +// if (!envValue.groups) return true +// if (!expectedValue.envs[envName]?.groups) return true + +// if (envValue.groups.toString() !== expectedValue.envs[envName]?.groups.toString()) +// return true + +// const envTenants = Object.keys(envValue.tenants) +// if (envTenants.length !== Object.keys(expectedValue.envs[envName]?.tenants ?? {}).length) +// return true + +// for (const tenantName of envTenants) { +// if (expectedValue.envs[envName] && !(tenantName in expectedValue.envs[envName].tenants)) +// return true +// } +// } +// return false +// }