From 18e7ce390128307dec322152f99ad1cc67665c78 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 31 Jan 2025 14:22:38 +0100 Subject: [PATCH 01/65] init kubero v3 refactoring --- server-refactored-v3/.gitignore | 56 + server-refactored-v3/.prettierrc | 4 + server-refactored-v3/README.md | 99 + server-refactored-v3/eslint.config.mjs | 35 + server-refactored-v3/nest-cli.json | 8 + server-refactored-v3/package.json | 73 + .../src/app.controller.spec.ts | 22 + server-refactored-v3/src/app.controller.ts | 12 + server-refactored-v3/src/app.module.ts | 10 + server-refactored-v3/src/app.service.ts | 8 + server-refactored-v3/src/main.ts | 8 + server-refactored-v3/test/app.e2e-spec.ts | 25 + server-refactored-v3/test/jest-e2e.json | 9 + server-refactored-v3/tsconfig.build.json | 4 + server-refactored-v3/tsconfig.json | 21 + server-refactored-v3/yarn.lock | 5720 +++++++++++++++++ 16 files changed, 6114 insertions(+) create mode 100644 server-refactored-v3/.gitignore create mode 100644 server-refactored-v3/.prettierrc create mode 100644 server-refactored-v3/README.md create mode 100644 server-refactored-v3/eslint.config.mjs create mode 100644 server-refactored-v3/nest-cli.json create mode 100644 server-refactored-v3/package.json create mode 100644 server-refactored-v3/src/app.controller.spec.ts create mode 100644 server-refactored-v3/src/app.controller.ts create mode 100644 server-refactored-v3/src/app.module.ts create mode 100644 server-refactored-v3/src/app.service.ts create mode 100644 server-refactored-v3/src/main.ts create mode 100644 server-refactored-v3/test/app.e2e-spec.ts create mode 100644 server-refactored-v3/test/jest-e2e.json create mode 100644 server-refactored-v3/tsconfig.build.json create mode 100644 server-refactored-v3/tsconfig.json create mode 100644 server-refactored-v3/yarn.lock diff --git a/server-refactored-v3/.gitignore b/server-refactored-v3/.gitignore new file mode 100644 index 00000000..4b56acfb --- /dev/null +++ b/server-refactored-v3/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/server-refactored-v3/.prettierrc b/server-refactored-v3/.prettierrc new file mode 100644 index 00000000..dcb72794 --- /dev/null +++ b/server-refactored-v3/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/server-refactored-v3/README.md b/server-refactored-v3/README.md new file mode 100644 index 00000000..c35976cb --- /dev/null +++ b/server-refactored-v3/README.md @@ -0,0 +1,99 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + Donate us + Support us + Follow us on Twitter +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Project setup + +```bash +$ yarn install +``` + +## Compile and run the project + +```bash +# development +$ yarn run start + +# watch mode +$ yarn run start:dev + +# production mode +$ yarn run start:prod +``` + +## Run tests + +```bash +# unit tests +$ yarn run test + +# e2e tests +$ yarn run test:e2e + +# test coverage +$ yarn run test:cov +``` + +## Deployment + +When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. + +If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: + +```bash +$ yarn install -g mau +$ mau deploy +``` + +With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. + +## Resources + +Check out a few resources that may come in handy when working with NestJS: + +- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. +- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). +- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). +- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. +- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). +- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). +- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). +- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/server-refactored-v3/eslint.config.mjs b/server-refactored-v3/eslint.config.mjs new file mode 100644 index 00000000..32465ccc --- /dev/null +++ b/server-refactored-v3/eslint.config.mjs @@ -0,0 +1,35 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['eslint.config.mjs'], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + ecmaVersion: 5, + sourceType: 'module', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn' + }, + }, +); \ No newline at end of file diff --git a/server-refactored-v3/nest-cli.json b/server-refactored-v3/nest-cli.json new file mode 100644 index 00000000..f9aa683b --- /dev/null +++ b/server-refactored-v3/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json new file mode 100644 index 00000000..a7c081a0 --- /dev/null +++ b/server-refactored-v3/package.json @@ -0,0 +1,73 @@ +{ + "name": "server-refactored-v3", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "@nestjs/platform-express": "^11.0.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@swc/cli": "^0.6.0", + "@swc/core": "^1.10.7", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.7", + "@types/supertest": "^6.0.2", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^15.14.0", + "jest": "^29.7.0", + "prettier": "^3.4.2", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/server-refactored-v3/src/app.controller.spec.ts b/server-refactored-v3/src/app.controller.spec.ts new file mode 100644 index 00000000..d22f3890 --- /dev/null +++ b/server-refactored-v3/src/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let appController: AppController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + + appController = app.get(AppController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts new file mode 100644 index 00000000..cce879ee --- /dev/null +++ b/server-refactored-v3/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getHello(): string { + return this.appService.getHello(); + } +} diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts new file mode 100644 index 00000000..86628031 --- /dev/null +++ b/server-refactored-v3/src/app.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/server-refactored-v3/src/app.service.ts b/server-refactored-v3/src/app.service.ts new file mode 100644 index 00000000..927d7cca --- /dev/null +++ b/server-refactored-v3/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getHello(): string { + return 'Hello World!'; + } +} diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts new file mode 100644 index 00000000..f76bc8d9 --- /dev/null +++ b/server-refactored-v3/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT ?? 3000); +} +bootstrap(); diff --git a/server-refactored-v3/test/app.e2e-spec.ts b/server-refactored-v3/test/app.e2e-spec.ts new file mode 100644 index 00000000..4df6580c --- /dev/null +++ b/server-refactored-v3/test/app.e2e-spec.ts @@ -0,0 +1,25 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { App } from 'supertest/types'; +import { AppModule } from './../src/app.module'; + +describe('AppController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/server-refactored-v3/test/jest-e2e.json b/server-refactored-v3/test/jest-e2e.json new file mode 100644 index 00000000..e9d912f3 --- /dev/null +++ b/server-refactored-v3/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/server-refactored-v3/tsconfig.build.json b/server-refactored-v3/tsconfig.build.json new file mode 100644 index 00000000..64f86c6b --- /dev/null +++ b/server-refactored-v3/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/server-refactored-v3/tsconfig.json b/server-refactored-v3/tsconfig.json new file mode 100644 index 00000000..21699639 --- /dev/null +++ b/server-refactored-v3/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock new file mode 100644 index 00000000..f19bdb69 --- /dev/null +++ b/server-refactored-v3/yarn.lock @@ -0,0 +1,5720 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@angular-devkit/core@19.0.1": + version "19.0.1" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-19.0.1.tgz#444e99e7684ee07c10d7c4e66377c3a4790e1438" + integrity sha512-oXIAV3hXqUW3Pmm95pvEmb+24n1cKQG62FzhQSjOIrMeHiCbGLNuc8zHosIi2oMrcCJJxR6KzWjThvbuzDwWlw== + dependencies: + ajv "8.17.1" + ajv-formats "3.0.1" + jsonc-parser "3.3.1" + picomatch "4.0.2" + rxjs "7.8.1" + source-map "0.7.4" + +"@angular-devkit/core@19.1.3": + version "19.1.3" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-19.1.3.tgz#79c3d6ece36ad2e5378e58ff79fe38c00c870fc5" + integrity sha512-of/TKfJ/vL+/qvr4PbDTtqbFJGFHPfu6bEJrIZsLMYA+Mej8SyTx3kDm4LLnKQBtWVYDqkrxvcpOb4+NmHNLfA== + dependencies: + ajv "8.17.1" + ajv-formats "3.0.1" + jsonc-parser "3.3.1" + picomatch "4.0.2" + rxjs "7.8.1" + source-map "0.7.4" + +"@angular-devkit/schematics-cli@19.1.3": + version "19.1.3" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-19.1.3.tgz#14a158edfda88c72a4f88c85bb3337221d0ad217" + integrity sha512-levMPch+Mni/cEVd/b9RUzasxWqlafBVjgrofbaSlxgZmr4pRJ/tihzrNnygNUaXoBqhTtXU5aFxTGbJhS35eA== + dependencies: + "@angular-devkit/core" "19.1.3" + "@angular-devkit/schematics" "19.1.3" + "@inquirer/prompts" "7.2.1" + ansi-colors "4.1.3" + symbol-observable "4.0.0" + yargs-parser "21.1.1" + +"@angular-devkit/schematics@19.0.1": + version "19.0.1" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-19.0.1.tgz#f6f6e30988c42184cc0ae921ee9747756a723baa" + integrity sha512-N9dV8WpNRULykNj8fSxQrta85gPKxb315J3xugLS2uwiFWhz7wo5EY1YeYhoVKoVcNB2ng9imJgC5aO52AHZwg== + dependencies: + "@angular-devkit/core" "19.0.1" + jsonc-parser "3.3.1" + magic-string "0.30.12" + ora "5.4.1" + rxjs "7.8.1" + +"@angular-devkit/schematics@19.1.3": + version "19.1.3" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-19.1.3.tgz#597eb3da85c9f2c1e6ae13264ad8f36673a093a7" + integrity sha512-DfN45eJQtfXXeQwjb7vDqSJ+8e6BW3rXUB2i6IC2CbOYrLWhMBgfv3/uTm++IbCFW2zX3Yk3yqq3d4yua2no7w== + dependencies: + "@angular-devkit/core" "19.1.3" + jsonc-parser "3.3.1" + magic-string "0.30.17" + ora "5.4.1" + rxjs "7.8.1" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/compat-data@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7" + integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.7.tgz#0439347a183b97534d52811144d763a17f9d2b24" + integrity sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.7" + "@babel/parser" "^7.26.7" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.26.7" + "@babel/types" "^7.26.7" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.26.5", "@babel/generator@^7.7.2": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458" + integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw== + dependencies: + "@babel/parser" "^7.26.5" + "@babel/types" "^7.26.5" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4" + integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.7" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.5", "@babel/parser@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.7.tgz#e114cd099e5f7d17b05368678da0fb9f69b3385c" + integrity sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w== + dependencies: + "@babel/types" "^7.26.7" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/template@^7.25.9", "@babel/template@^7.3.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb" + integrity sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/parser" "^7.26.7" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.7" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.5", "@babel/types@^7.26.7", "@babel/types@^7.3.3": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.7.tgz#5e2b89c0768e874d4d061961f3a5a153d71dc17a" + integrity sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.1.tgz#734aaea2c40be22bbb1f2a9dac687c57a6a4c984" + integrity sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA== + dependencies: + "@eslint/object-schema" "^2.1.5" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" + integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.19.0", "@eslint/js@^9.18.0": + version "9.19.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.19.0.tgz#51dbb140ed6b49d05adc0b171c41e1a8713b7789" + integrity sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ== + +"@eslint/object-schema@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.5.tgz#8670a8f6258a2be5b2c620ff314a1d984c23eb2e" + integrity sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ== + +"@eslint/plugin-kit@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" + integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== + dependencies: + "@eslint/core" "^0.10.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== + +"@inquirer/checkbox@^4.0.4", "@inquirer/checkbox@^4.0.6": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-4.0.7.tgz#4c11322ab932765cace50d163eea73002dd76432" + integrity sha512-lyoF4uYdBBTnqeB1gjPdYkiQ++fz/iYKaP9DON1ZGlldkvAEJsjaOBRdbl5UW1pOSslBRd701jxhAG0MlhHd2w== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/figures" "^1.0.10" + "@inquirer/type" "^3.0.3" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/confirm@^5.1.1", "@inquirer/confirm@^5.1.3": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.4.tgz#3e2c9bfdf80331676196d8dbb2261103a67d0e9d" + integrity sha512-EsiT7K4beM5fN5Mz6j866EFA9+v9d5o9VUra3hrg8zY4GHmCS8b616FErbdo5eyKoVotBQkHzMIeeKYsKDStDw== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + +"@inquirer/core@^10.1.5": + version "10.1.5" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.5.tgz#7271c177340f77c2e231704227704d8cdf497747" + integrity sha512-/vyCWhET0ktav/mUeBqJRYTwmjFPIKPRYb3COAw7qORULgipGSUO2vL32lQKki3UxDKJ8BvuEbokaoyCA6YlWw== + dependencies: + "@inquirer/figures" "^1.0.10" + "@inquirer/type" "^3.0.3" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^2.0.0" + signal-exit "^4.1.0" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/editor@^4.2.1", "@inquirer/editor@^4.2.3": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-4.2.4.tgz#1b2b6c0088c80375df1d7d2de89c30088b2bfe29" + integrity sha512-S8b6+K9PLzxiFGGc02m4syhEu5JsH0BukzRsuZ+tpjJ5aDsDX1WfNfOil2fmsO36Y1RMcpJGxlfQ1yh4WfU28Q== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + external-editor "^3.1.0" + +"@inquirer/expand@^4.0.4", "@inquirer/expand@^4.0.6": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-4.0.7.tgz#352e05407e72e2f079e5affe032cc77c93ff7501" + integrity sha512-PsUQ5t7r+DPjW0VVEHzssOTBM2UPHnvBNse7hzuki7f6ekRL94drjjfBLrGEDe7cgj3pguufy/cuFwMeWUWHXw== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.10.tgz#e3676a51c9c51aaabcd6ba18a28e82b98417db37" + integrity sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw== + +"@inquirer/input@^4.1.1", "@inquirer/input@^4.1.3": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-4.1.4.tgz#10080f9a4b258c3d3a066488804bfb4caf5529fc" + integrity sha512-CKKF8otRBdIaVnRxkFLs00VNA9HWlEh3x4SqUfC3A8819TeOZpTYG/p+4Nqu3hh97G+A0lxkOZNYE7KISgU8BA== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + +"@inquirer/number@^3.0.4", "@inquirer/number@^3.0.6": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-3.0.7.tgz#50bc394cda68205025e098b0cdec716f6d100e56" + integrity sha512-uU2nmXGC0kD8+BLgwZqcgBD1jcw2XFww2GmtP6b4504DkOp+fFAhydt7JzRR1TAI2dmj175p4SZB0lxVssNreA== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + +"@inquirer/password@^4.0.4", "@inquirer/password@^4.0.6": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-4.0.7.tgz#28a908185da7d65cf24b0e8e44c7ecc73b703889" + integrity sha512-DFpqWLx+C5GV5zeFWuxwDYaeYnTWYphO07pQ2VnP403RIqRIpwBG0ATWf7pF+3IDbaXEtWatCJWxyDrJ+rkj2A== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-7.2.1.tgz#f00fbcf06998a07faebc10741efa289384529950" + integrity sha512-v2JSGri6/HXSfoGIwuKEn8sNCQK6nsB2BNpy2lSX6QH9bsECrMv93QHnj5+f+1ZWpF/VNioIV2B/PDox8EvGuQ== + dependencies: + "@inquirer/checkbox" "^4.0.4" + "@inquirer/confirm" "^5.1.1" + "@inquirer/editor" "^4.2.1" + "@inquirer/expand" "^4.0.4" + "@inquirer/input" "^4.1.1" + "@inquirer/number" "^3.0.4" + "@inquirer/password" "^4.0.4" + "@inquirer/rawlist" "^4.0.4" + "@inquirer/search" "^3.0.4" + "@inquirer/select" "^4.0.4" + +"@inquirer/prompts@7.2.3": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-7.2.3.tgz#8a0d7cb5310d429bf815d25bbff108375fc6315b" + integrity sha512-hzfnm3uOoDySDXfDNOm9usOuYIaQvTgKp/13l1uJoe6UNY+Zpcn2RYt0jXz3yA+yemGHvDOxVzqWl3S5sQq53Q== + dependencies: + "@inquirer/checkbox" "^4.0.6" + "@inquirer/confirm" "^5.1.3" + "@inquirer/editor" "^4.2.3" + "@inquirer/expand" "^4.0.6" + "@inquirer/input" "^4.1.3" + "@inquirer/number" "^3.0.6" + "@inquirer/password" "^4.0.6" + "@inquirer/rawlist" "^4.0.6" + "@inquirer/search" "^3.0.6" + "@inquirer/select" "^4.0.6" + +"@inquirer/rawlist@^4.0.4", "@inquirer/rawlist@^4.0.6": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-4.0.7.tgz#b6c710a6a1c3dc8891b313d1b901367b4fc0df31" + integrity sha512-ZeBca+JCCtEIwQMvhuROT6rgFQWWvAImdQmIIP3XoyDFjrp2E0gZlEn65sWIoR6pP2EatYK96pvx0887OATWQQ== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^3.0.4", "@inquirer/search@^3.0.6": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-3.0.7.tgz#78ec82bc0597fb27ac6bf306e4602e607a06a0b3" + integrity sha512-Krq925SDoLh9AWSNee8mbSIysgyWtcPnSAp5YtPBGCQ+OCO+5KGC8FwLpyxl8wZ2YAov/8Tp21stTRK/fw5SGg== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/figures" "^1.0.10" + "@inquirer/type" "^3.0.3" + yoctocolors-cjs "^2.1.2" + +"@inquirer/select@^4.0.4", "@inquirer/select@^4.0.6": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-4.0.7.tgz#cea50dc7a00e749386d23ac42487dd62f7379d84" + integrity sha512-ejGBMDSD+Iqk60u5t0Zf2UQhGlJWDM78Ep70XpNufIfc+f4VOTeybYKXu9pDjz87FkRzLiVsGpQG2SzuGlhaJw== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/figures" "^1.0.10" + "@inquirer/type" "^3.0.3" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/type@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.3.tgz#aa9cb38568f23f772b417c972f6a2d906647a6af" + integrity sha512-I4VIHFxUuY1bshGbXZTxCmhwaaEst9s/lll3ekok+o1Z26/ZUKdx8y1b7lsoG6rtsBDwEGfiBJ2SfirjoISLpg== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@lukeed/csprng@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + +"@napi-rs/nice-android-arm-eabi@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz#9a0cba12706ff56500df127d6f4caf28ddb94936" + integrity sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w== + +"@napi-rs/nice-android-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz#32fc32e9649bd759d2a39ad745e95766f6759d2f" + integrity sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA== + +"@napi-rs/nice-darwin-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz#d3c44c51b94b25a82d45803e2255891e833e787b" + integrity sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA== + +"@napi-rs/nice-darwin-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz#f1b1365a8370c6a6957e90085a9b4873d0e6a957" + integrity sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ== + +"@napi-rs/nice-freebsd-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz#4280f081efbe0b46c5165fdaea8b286e55a8f89e" + integrity sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw== + +"@napi-rs/nice-linux-arm-gnueabihf@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz#07aec23a9467ed35eb7602af5e63d42c5d7bd473" + integrity sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q== + +"@napi-rs/nice-linux-arm64-gnu@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz#038a77134cc6df3c48059d5a5e199d6f50fb9a90" + integrity sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA== + +"@napi-rs/nice-linux-arm64-musl@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz#715d0906582ba0cff025109f42e5b84ea68c2bcc" + integrity sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw== + +"@napi-rs/nice-linux-ppc64-gnu@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz#ac1c8f781c67b0559fa7a1cd4ae3ca2299dc3d06" + integrity sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q== + +"@napi-rs/nice-linux-riscv64-gnu@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz#b0a430549acfd3920ffd28ce544e2fe17833d263" + integrity sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig== + +"@napi-rs/nice-linux-s390x-gnu@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz#5b95caf411ad72a965885217db378c4d09733e97" + integrity sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg== + +"@napi-rs/nice-linux-x64-gnu@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz#a98cdef517549f8c17a83f0236a69418a90e77b7" + integrity sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA== + +"@napi-rs/nice-linux-x64-musl@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz#5e26843eafa940138aed437c870cca751c8a8957" + integrity sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ== + +"@napi-rs/nice-win32-arm64-msvc@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz#bd62617d02f04aa30ab1e9081363856715f84cd8" + integrity sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg== + +"@napi-rs/nice-win32-ia32-msvc@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz#b8b7aad552a24836027473d9b9f16edaeabecf18" + integrity sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw== + +"@napi-rs/nice-win32-x64-msvc@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz#37d8718b8f722f49067713e9f1e85540c9a3dd09" + integrity sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg== + +"@napi-rs/nice@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@napi-rs/nice/-/nice-1.0.1.tgz#483d3ff31e5661829a1efb4825591a135c3bfa7d" + integrity sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ== + optionalDependencies: + "@napi-rs/nice-android-arm-eabi" "1.0.1" + "@napi-rs/nice-android-arm64" "1.0.1" + "@napi-rs/nice-darwin-arm64" "1.0.1" + "@napi-rs/nice-darwin-x64" "1.0.1" + "@napi-rs/nice-freebsd-x64" "1.0.1" + "@napi-rs/nice-linux-arm-gnueabihf" "1.0.1" + "@napi-rs/nice-linux-arm64-gnu" "1.0.1" + "@napi-rs/nice-linux-arm64-musl" "1.0.1" + "@napi-rs/nice-linux-ppc64-gnu" "1.0.1" + "@napi-rs/nice-linux-riscv64-gnu" "1.0.1" + "@napi-rs/nice-linux-s390x-gnu" "1.0.1" + "@napi-rs/nice-linux-x64-gnu" "1.0.1" + "@napi-rs/nice-linux-x64-musl" "1.0.1" + "@napi-rs/nice-win32-arm64-msvc" "1.0.1" + "@napi-rs/nice-win32-ia32-msvc" "1.0.1" + "@napi-rs/nice-win32-x64-msvc" "1.0.1" + +"@nestjs/cli@^11.0.0": + version "11.0.2" + resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-11.0.2.tgz#dff9b0bda813b141c1f4c19fdc9a0138d66eeacc" + integrity sha512-y1dKk+Q94vnWhJe8eoz1Qs5WIYHSgO0xZttsFnDbYW1A6CBUVanc4RocbiyhwC/GjWPO4D5JmTXjW5mRH6wprA== + dependencies: + "@angular-devkit/core" "19.1.3" + "@angular-devkit/schematics" "19.1.3" + "@angular-devkit/schematics-cli" "19.1.3" + "@inquirer/prompts" "7.2.3" + "@nestjs/schematics" "11.0.0" + ansis "3.9.0" + chokidar "4.0.3" + cli-table3 "0.6.5" + commander "4.1.1" + fork-ts-checker-webpack-plugin "9.0.2" + glob "11.0.1" + node-emoji "1.11.0" + ora "5.4.1" + tree-kill "1.2.2" + tsconfig-paths "4.2.0" + tsconfig-paths-webpack-plugin "4.2.0" + typescript "5.7.3" + webpack "5.97.1" + webpack-node-externals "3.0.0" + +"@nestjs/common@^11.0.1": + version "11.0.6" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.0.6.tgz#ddd534e437e6ef581b06d38cfc9e97a6ed40b14b" + integrity sha512-j+M3WOU6loZPNirIHDiZ1LxXRXVNb62XicgLBqdgyrDBFCJrAZaq0lfERUEPlN0/j4GBFnTSPg+CNsoGTBW1zQ== + dependencies: + uid "2.0.2" + iterare "1.2.1" + tslib "2.8.1" + +"@nestjs/core@^11.0.1": + version "11.0.6" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-11.0.6.tgz#e7e55aa5ab3fa8d2ea9de2b149b97596fb5b6e6f" + integrity sha512-Xf33bwc3waAJ/faJBW06+Dwq3m15p3wbFOc/CcK8ua5EZna4sMjIjXXAb6bQmEjR1KfTXV5z595UD2vwp6cyHg== + dependencies: + uid "2.0.2" + "@nuxt/opencollective" "0.4.1" + fast-safe-stringify "2.1.1" + iterare "1.2.1" + path-to-regexp "8.2.0" + tslib "2.8.1" + +"@nestjs/platform-express@^11.0.1": + version "11.0.6" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-11.0.6.tgz#4ab7b81078ab63175db874a939513ddac1f9a08d" + integrity sha512-fP6vrpqDIBaf1FNfFtBeJm/BwtGtueatI4FHxaBgw93XxKmIOV4G4ZO7ouQKqfgyIxV2mkYr/Fhg7hwRmizIjw== + dependencies: + cors "2.8.5" + express "5.0.1" + multer "1.4.5-lts.1" + path-to-regexp "8.2.0" + tslib "2.8.1" + +"@nestjs/schematics@11.0.0", "@nestjs/schematics@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-11.0.0.tgz#8e02e86d6515e57eac72923ebae330f57c0ae390" + integrity sha512-wts8lG0GfNWw3Wk9aaG5I/wcMIAdm7HjjeThQfUZhJxeIFT82Z3F5+0cYdHH4ii2pYQGiCSrR1VcuMwPiHoecg== + dependencies: + "@angular-devkit/core" "19.0.1" + "@angular-devkit/schematics" "19.0.1" + comment-json "4.2.5" + jsonc-parser "3.3.1" + pluralize "8.0.0" + +"@nestjs/testing@^11.0.1": + version "11.0.6" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-11.0.6.tgz#48840eefffd1455ea74fead5c23f33890ed7577d" + integrity sha512-RZDWdnOncOQ1vT3630VlRzKee2P21ZJoF1+NAY+nzYUuYuYAaBdjrTZQGwymmiZQcrM+TQaViSjSPUmcJXdKyA== + dependencies: + tslib "2.8.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@nuxt/opencollective@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.4.1.tgz#57bc41d2b03b2fba20b935c15950ac0f4bd2cea2" + integrity sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ== + dependencies: + consola "^3.2.3" + +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + +"@sec-ant/readable-stream@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" + integrity sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^5.2.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668" + integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@swc/cli@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@swc/cli/-/cli-0.6.0.tgz#fe986a436797c9d3850938366dbd660c9ba1101f" + integrity sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw== + dependencies: + "@swc/counter" "^0.1.3" + "@xhmikosr/bin-wrapper" "^13.0.5" + commander "^8.3.0" + fast-glob "^3.2.5" + minimatch "^9.0.3" + piscina "^4.3.1" + semver "^7.3.8" + slash "3.0.0" + source-map "^0.7.3" + +"@swc/core-darwin-arm64@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.12.tgz#ed317cd6aac5a66f529c0cbd8385761e2eccecc6" + integrity sha512-pOANQegUTAriW7jq3SSMZGM5l89yLVMs48R0F2UG6UZsH04SiViCnDctOGlA/Sa++25C+rL9MGMYM1jDLylBbg== + +"@swc/core-darwin-x64@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.10.12.tgz#59e249f40852231232b80f6a4caea2a223e9682e" + integrity sha512-m4kbpIDDsN1FrwfNQMU+FTrss356xsXvatLbearwR+V0lqOkjLBP0VmRvQfHEg+uy13VPyrT9gj4HLoztlci7w== + +"@swc/core-linux-arm-gnueabihf@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.12.tgz#5c2066a6ad8b768adc473e300995f909ce96cbbd" + integrity sha512-OY9LcupgqEu8zVK+rJPes6LDJJwPDmwaShU96beTaxX2K6VrXbpwm5WbPS/8FfQTsmpnuA7dCcMPUKhNgmzTrQ== + +"@swc/core-linux-arm64-gnu@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.12.tgz#7a8e6212617365c41a7b6e015cd2749d222c1ebe" + integrity sha512-nJD587rO0N4y4VZszz3xzVr7JIiCzSMhEMWnPjuh+xmPxDBz0Qccpr8xCr1cSxpl1uY7ERkqAGlKr6CwoV5kVg== + +"@swc/core-linux-arm64-musl@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.12.tgz#c939d554ecb32df65b4a784fc586f30c8ae7be0a" + integrity sha512-oqhSmV+XauSf0C//MoQnVErNUB/5OzmSiUzuazyLsD5pwqKNN+leC3JtRQ/QVzaCpr65jv9bKexT9+I2Tt3xDw== + +"@swc/core-linux-x64-gnu@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.12.tgz#745bc25de364bbde3b6240ed84d063379dc52c6c" + integrity sha512-XldSIHyjD7m1Gh+/8rxV3Ok711ENLI420CU2EGEqSe3VSGZ7pHJvJn9ZFbYpWhsLxPqBYMFjp3Qw+J6OXCPXCA== + +"@swc/core-linux-x64-musl@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.12.tgz#188855ee612a482eb8c298b2237e0b36962182a7" + integrity sha512-wvPXzJxzPgTqhyp1UskOx1hRTtdWxlyFD1cGWOxgLsMik0V9xKRgqKnMPv16Nk7L9xl6quQ6DuUHj9ID7L3oVw== + +"@swc/core-win32-arm64-msvc@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.12.tgz#3f8271b8a42ef29b53574705a6cd0427345cc616" + integrity sha512-TUYzWuu1O7uyIcRfxdm6Wh1u+gNnrW5M1DUgDOGZLsyQzgc2Zjwfh2llLhuAIilvCVg5QiGbJlpibRYJ/8QGsg== + +"@swc/core-win32-ia32-msvc@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.12.tgz#b7f59376870039f6a7ecc5331b4287f0fa82b182" + integrity sha512-4Qrw+0Xt+Fe2rz4OJ/dEPMeUf/rtuFWWAj/e0vL7J5laUHirzxawLRE5DCJLQTarOiYR6mWnmadt9o3EKzV6Xg== + +"@swc/core-win32-x64-msvc@1.10.12": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.12.tgz#e053b1efc2bf24b0da1a1fa6a5ea6e1bda36df76" + integrity sha512-YiloZXLW7rUxJpALwHXaGjVaAEn+ChoblG7/3esque+Y7QCyheoBUJp2DVM1EeVA43jBfZ8tvYF0liWd9Tpz1A== + +"@swc/core@^1.10.7": + version "1.10.12" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.10.12.tgz#6d002050814888ec72a8d439ca7194a4631ce199" + integrity sha512-+iUL0PYpPm6N9AdV1wvafakvCqFegQus1aoEDxgFsv3/uNVNIyRaupf/v/Zkp5hbep2EzhtoJR0aiJIzDbXWHg== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.17" + optionalDependencies: + "@swc/core-darwin-arm64" "1.10.12" + "@swc/core-darwin-x64" "1.10.12" + "@swc/core-linux-arm-gnueabihf" "1.10.12" + "@swc/core-linux-arm64-gnu" "1.10.12" + "@swc/core-linux-arm64-musl" "1.10.12" + "@swc/core-linux-x64-gnu" "1.10.12" + "@swc/core-linux-x64-musl" "1.10.12" + "@swc/core-win32-arm64-msvc" "1.10.12" + "@swc/core-win32-ia32-msvc" "1.10.12" + "@swc/core-win32-x64-msvc" "1.10.12" + +"@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/types@^0.1.17": + version "0.1.17" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.17.tgz#bd1d94e73497f27341bf141abdf4c85230d41e7c" + integrity sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ== + dependencies: + "@swc/counter" "^0.1.3" + +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" + integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + dependencies: + defer-to-connect "^2.0.1" + +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cookiejar@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" + integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/express-serve-static-core@^5.0.0": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz#41fec4ea20e9c7b22f024ab88a95c6bb288f51b8" + integrity sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c" + integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/http-cache-semantics@^4.0.2": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.14": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/methods@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" + integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node@*", "@types/node@^22.10.7": + version "22.12.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.12.0.tgz#bf8af3b2af0837b5a62a368756ff2b705ae0048c" + integrity sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA== + dependencies: + undici-types "~6.20.0" + +"@types/qs@*": + version "6.9.18" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2" + integrity sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/superagent@^8.1.0": + version "8.1.9" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" + integrity sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ== + dependencies: + "@types/cookiejar" "^2.1.5" + "@types/methods" "^1.1.4" + "@types/node" "*" + form-data "^4.0.0" + +"@types/supertest@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.2.tgz#2af1c466456aaf82c7c6106c6b5cbd73a5e86588" + integrity sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg== + dependencies: + "@types/methods" "^1.1.4" + "@types/superagent" "^8.1.0" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz#63a1b0d24d85a971949f8d71d693019f58d2e861" + integrity sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.22.0" + "@typescript-eslint/type-utils" "8.22.0" + "@typescript-eslint/utils" "8.22.0" + "@typescript-eslint/visitor-keys" "8.22.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.0" + +"@typescript-eslint/parser@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.22.0.tgz#f21c5db24271f182ebbb4ba8c7ad3eb76e5f5f3a" + integrity sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ== + dependencies: + "@typescript-eslint/scope-manager" "8.22.0" + "@typescript-eslint/types" "8.22.0" + "@typescript-eslint/typescript-estree" "8.22.0" + "@typescript-eslint/visitor-keys" "8.22.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz#e85836ddeb8eae715f870628bcc32fe96aaf4d0e" + integrity sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ== + dependencies: + "@typescript-eslint/types" "8.22.0" + "@typescript-eslint/visitor-keys" "8.22.0" + +"@typescript-eslint/type-utils@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz#cd9f23c23f021357ef0baa3490d4d96edcc97509" + integrity sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA== + dependencies: + "@typescript-eslint/typescript-estree" "8.22.0" + "@typescript-eslint/utils" "8.22.0" + debug "^4.3.4" + ts-api-utils "^2.0.0" + +"@typescript-eslint/types@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.22.0.tgz#d9dec7116479ad03aeb6c8ac9c5223c4c79cf360" + integrity sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A== + +"@typescript-eslint/typescript-estree@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz#c188c3e19529d5b3145577c0bd967e2683b114df" + integrity sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w== + dependencies: + "@typescript-eslint/types" "8.22.0" + "@typescript-eslint/visitor-keys" "8.22.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.0" + +"@typescript-eslint/utils@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.22.0.tgz#c8cc4e52a9c711af8a741a82dc5d7242b7a8dd44" + integrity sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.22.0" + "@typescript-eslint/types" "8.22.0" + "@typescript-eslint/typescript-estree" "8.22.0" + +"@typescript-eslint/visitor-keys@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz#02cc005014c372033eb9171e2275b76cba722a3f" + integrity sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w== + dependencies: + "@typescript-eslint/types" "8.22.0" + eslint-visitor-keys "^4.2.0" + +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== + +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" + +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@xtuc/long" "4.2.2" + +"@xhmikosr/archive-type@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@xhmikosr/archive-type/-/archive-type-7.0.0.tgz#74746a210b59d7d8a77aa69a422f0dae025b3798" + integrity sha512-sIm84ZneCOJuiy3PpWR5bxkx3HaNt1pqaN+vncUBZIlPZCq8ASZH+hBVdu5H8znR7qYC6sKwx+ie2Q7qztJTxA== + dependencies: + file-type "^19.0.0" + +"@xhmikosr/bin-check@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@xhmikosr/bin-check/-/bin-check-7.0.3.tgz#9ce53f339db419f08e799f4c55b82b38ede13c95" + integrity sha512-4UnCLCs8DB+itHJVkqFp9Zjg+w/205/J2j2wNBsCEAm/BuBmtua2hhUOdAMQE47b1c7P9Xmddj0p+X1XVsfHsA== + dependencies: + execa "^5.1.1" + isexe "^2.0.0" + +"@xhmikosr/bin-wrapper@^13.0.5": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@xhmikosr/bin-wrapper/-/bin-wrapper-13.0.5.tgz#2f5804ac0a3331df11d76d08dab3a3eb674ef0df" + integrity sha512-DT2SAuHDeOw0G5bs7wZbQTbf4hd8pJ14tO0i4cWhRkIJfgRdKmMfkDilpaJ8uZyPA0NVRwasCNAmMJcWA67osw== + dependencies: + "@xhmikosr/bin-check" "^7.0.3" + "@xhmikosr/downloader" "^15.0.1" + "@xhmikosr/os-filter-obj" "^3.0.0" + bin-version-check "^5.1.0" + +"@xhmikosr/decompress-tar@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@xhmikosr/decompress-tar/-/decompress-tar-8.0.1.tgz#ca9cc65453b5ac59bb5eb897b6f1390a4905b565" + integrity sha512-dpEgs0cQKJ2xpIaGSO0hrzz3Kt8TQHYdizHsgDtLorWajuHJqxzot9Hbi0huRxJuAGG2qiHSQkwyvHHQtlE+fg== + dependencies: + file-type "^19.0.0" + is-stream "^2.0.1" + tar-stream "^3.1.7" + +"@xhmikosr/decompress-tarbz2@^8.0.1": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.0.2.tgz#1c19b4a59585321a7c64ab0ff1f85f92b66fca1a" + integrity sha512-p5A2r/AVynTQSsF34Pig6olt9CvRj6J5ikIhzUd3b57pUXyFDGtmBstcw+xXza0QFUh93zJsmY3zGeNDlR2AQQ== + dependencies: + "@xhmikosr/decompress-tar" "^8.0.1" + file-type "^19.6.0" + is-stream "^2.0.1" + seek-bzip "^2.0.0" + unbzip2-stream "^1.4.3" + +"@xhmikosr/decompress-targz@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@xhmikosr/decompress-targz/-/decompress-targz-8.0.1.tgz#54dbd48e83861db43857970c2fcdbd431371e95b" + integrity sha512-mvy5AIDIZjQ2IagMI/wvauEiSNHhu/g65qpdM4EVoYHUJBAmkQWqcPJa8Xzi1aKVTmOA5xLJeDk7dqSjlHq8Mg== + dependencies: + "@xhmikosr/decompress-tar" "^8.0.1" + file-type "^19.0.0" + is-stream "^2.0.1" + +"@xhmikosr/decompress-unzip@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@xhmikosr/decompress-unzip/-/decompress-unzip-7.0.0.tgz#dcf9417829bf9fe474f6064513017949915e14c0" + integrity sha512-GQMpzIpWTsNr6UZbISawsGI0hJ4KA/mz5nFq+cEoPs12UybAqZWKbyIaZZyLbJebKl5FkLpsGBkrplJdjvUoSQ== + dependencies: + file-type "^19.0.0" + get-stream "^6.0.1" + yauzl "^3.1.2" + +"@xhmikosr/decompress@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@xhmikosr/decompress/-/decompress-10.0.1.tgz#63650498b4f3dd0fb5ee645dc5a35e1a7baad632" + integrity sha512-6uHnEEt5jv9ro0CDzqWlFgPycdE+H+kbJnwyxgZregIMLQ7unQSCNVsYG255FoqU8cP46DyggI7F7LohzEl8Ag== + dependencies: + "@xhmikosr/decompress-tar" "^8.0.1" + "@xhmikosr/decompress-tarbz2" "^8.0.1" + "@xhmikosr/decompress-targz" "^8.0.1" + "@xhmikosr/decompress-unzip" "^7.0.0" + graceful-fs "^4.2.11" + make-dir "^4.0.0" + strip-dirs "^3.0.0" + +"@xhmikosr/downloader@^15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@xhmikosr/downloader/-/downloader-15.0.1.tgz#5dd34cf8aa8ce5f1e156e03188f7ba65abfa45c6" + integrity sha512-fiuFHf3Dt6pkX8HQrVBsK0uXtkgkVlhrZEh8b7VgoDqFf+zrgFBPyrwCqE/3nDwn3hLeNz+BsrS7q3mu13Lp1g== + dependencies: + "@xhmikosr/archive-type" "^7.0.0" + "@xhmikosr/decompress" "^10.0.1" + content-disposition "^0.5.4" + defaults "^3.0.0" + ext-name "^5.0.0" + file-type "^19.0.0" + filenamify "^6.0.0" + get-stream "^6.0.1" + got "^13.0.0" + +"@xhmikosr/os-filter-obj@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz#917d380868d03ce853f90a919716ef73f6b26808" + integrity sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A== + dependencies: + arch "^3.0.0" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.2: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +ajv-formats@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@8.17.1, ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +ansis@3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.9.0.tgz#d195c93c31a333916142ff8f0be4d7e3872f262e" + integrity sha512-PcDrVe15ldexeZMsVLBAzBwF2KhZgaU0R+CHxH+x5kqn/pO+UWVBZJ+NEXMPpEOLUFeNsnNdoWYc2gwO+MVkDg== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + +arch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-3.0.0.tgz#a44e7077da4615fc5f1e3da21fbfc201d2c1817c" + integrity sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541" + integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== + +array-timsort@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" + integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +b4a@^1.6.4: + version "1.6.7" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" + integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bare-events@^2.2.0: + version "2.5.4" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745" + integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bin-version-check@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-5.1.0.tgz#788e80e036a87313f8be7908bc20e5abe43f0837" + integrity sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g== + dependencies: + bin-version "^6.0.0" + semver "^7.5.3" + semver-truncate "^3.0.0" + +bin-version@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-6.0.0.tgz#08ecbe5fc87898b441425e145f9e105064d00315" + integrity sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw== + dependencies: + execa "^5.0.0" + find-versions "^5.0.0" + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +body-parser@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.0.2.tgz#52a90ca70bfafae03210b5b998e4ffcc3ecaecae" + integrity sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "3.1.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.5.2" + on-finished "2.4.1" + qs "6.13.0" + raw-body "^3.0.0" + type-is "~1.6.18" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.2.1, buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +busboy@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacheable-lookup@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" + integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w== + +cacheable-request@^10.2.8: + version "10.2.14" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-10.2.14.tgz#eb915b665fda41b79652782df3f553449c406b9d" + integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ== + dependencies: + "@types/http-cache-semantics" "^4.0.2" + get-stream "^6.0.1" + http-cache-semantics "^4.1.1" + keyv "^4.5.3" + mimic-response "^4.0.0" + normalize-url "^8.0.0" + responselike "^3.0.0" + +call-bind-apply-helpers@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" + integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681" + integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== + dependencies: + call-bind-apply-helpers "^1.0.1" + get-intrinsic "^1.2.6" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001688: + version "1.0.30001696" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz#00c30a2fc11e3c98c25e5125418752af3ae2f49f" + integrity sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ== + +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cli-table3@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +comment-json@4.2.5: + version "4.2.5" + resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.2.5.tgz#482e085f759c2704b60bc6f97f55b8c01bc41e70" + integrity sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw== + dependencies: + array-timsort "^1.0.3" + core-util-is "^1.0.3" + esprima "^4.0.1" + has-own-prop "^2.0.0" + repeat-string "^1.6.1" + +component-emitter@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +consola@^3.2.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.0.tgz#4cfc9348fd85ed16a17940b3032765e31061ab88" + integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA== + +content-disposition@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-disposition@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" + integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + +cookiejar@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + +core-util-is@^1.0.3, core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cors@2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cosmiconfig@^8.2.0: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + +defaults@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-3.0.0.tgz#60b9e0003df1018737c2ce3f4289d8f64786c9c4" + integrity sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A== + +defer-to-connect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0, destroy@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.73: + version "1.5.90" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz#4717e5a5413f95bbb12d0af14c35057e9c65e0b6" + integrity sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encodeurl@^2.0.0, encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: + version "5.18.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz#91eb1db193896b9801251eeff1c6980278b1e404" + integrity sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + +es-object-atoms@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz#fbb03bfc8db0651df9ce4e8b7150d11c5fe3addf" + integrity sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw== + +eslint-plugin-prettier@^5.2.2: + version "5.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz#c4af01691a6fa9905207f0fbba0d7bea0902cce5" + integrity sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.9.1" + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.18.0: + version "9.19.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.19.0.tgz#ffa1d265fc4205e0f8464330d35f09e1d548b1bf" + integrity sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.10.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.19.0" + "@eslint/plugin-kit" "^0.2.5" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@^1.8.1, etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0, execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + 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" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +express@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.0.1.tgz#5d359a2550655be33124ecbc7400cd38436457e9" + integrity sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ== + dependencies: + accepts "^2.0.0" + body-parser "^2.0.1" + content-disposition "^1.0.0" + content-type "~1.0.4" + cookie "0.7.1" + cookie-signature "^1.2.1" + debug "4.3.6" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "^2.0.0" + fresh "2.0.0" + http-errors "2.0.0" + merge-descriptors "^2.0.0" + methods "~1.1.2" + mime-types "^3.0.0" + on-finished "2.4.1" + once "1.4.0" + parseurl "~1.3.3" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + router "^2.0.0" + safe-buffer "5.2.1" + send "^1.1.0" + serve-static "^2.1.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "^2.0.0" + utils-merge "1.0.1" + vary "~1.1.2" + +ext-list@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" + integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== + dependencies: + mime-db "^1.28.0" + +ext-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" + integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== + dependencies: + ext-list "^2.0.0" + sort-keys-length "^1.0.0" + +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-fifo@^1.2.0, fast-fifo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + +fast-glob@^3.2.5, fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + +fastq@^1.6.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.0.tgz#a82c6b7c2bb4e44766d865f07997785fecfdcb89" + integrity sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +file-type@^19.0.0, file-type@^19.6.0: + version "19.6.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-19.6.0.tgz#b43d8870453363891884cf5e79bb3e4464f2efd3" + integrity sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ== + dependencies: + get-stream "^9.0.1" + strtok3 "^9.0.1" + token-types "^6.0.0" + uint8array-extras "^1.3.0" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +filename-reserved-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz#3d5dd6d4e2d73a3fed2ebc4cd0b3448869a081f7" + integrity sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw== + +filenamify@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-6.0.0.tgz#38def94098c62154c42a41d822650f5f55bcbac2" + integrity sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ== + dependencies: + filename-reserved-regex "^3.0.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.0.0.tgz#9d3c79156dfa798069db7de7dd53bc37546f564b" + integrity sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-versions@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-5.1.0.tgz#973f6739ce20f5e439a27eba8542a4b236c8e685" + integrity sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg== + dependencies: + semver-regex "^4.0.5" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +fork-ts-checker-webpack-plugin@9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz#c12c590957837eb02b02916902dcf3e675fd2b1e" + integrity sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg== + dependencies: + "@babel/code-frame" "^7.16.7" + chalk "^4.1.2" + chokidar "^3.5.3" + cosmiconfig "^8.2.0" + deepmerge "^4.2.2" + fs-extra "^10.0.0" + memfs "^3.4.1" + minimatch "^3.0.4" + node-abort-controller "^3.0.1" + schema-utils "^3.1.1" + semver "^7.3.5" + tapable "^2.2.1" + +form-data-encoder@^2.1.2: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" + integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formidable@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.2.tgz#207c33fecdecb22044c82ba59d0c63a12fb81d77" + integrity sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg== + dependencies: + dezalgo "^1.0.4" + hexoid "^2.0.0" + once "^1.4.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fresh@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.2.5, get-intrinsic@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" + integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + function-bind "^1.1.2" + get-proto "^1.0.0" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-stream@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-9.0.1.tgz#95157d21df8eb90d1647102b63039b1df60ebd27" + integrity sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA== + dependencies: + "@sec-ant/readable-stream" "^0.4.1" + is-stream "^4.0.1" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.1.tgz#1c3aef9a59d680e611b53dcd24bb8639cef064d9" + integrity sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw== + dependencies: + foreground-child "^3.1.0" + jackspeak "^4.0.1" + minimatch "^10.0.0" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globals@^15.14.0: + version "15.14.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.14.0.tgz#b8fd3a8941ff3b4d38f3319d433b61bbb482e73f" + integrity sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig== + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +got@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/got/-/got-13.0.0.tgz#a2402862cef27a5d0d1b07c0fb25d12b58175422" + integrity sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA== + dependencies: + "@sindresorhus/is" "^5.2.0" + "@szmarczak/http-timer" "^5.0.1" + cacheable-lookup "^7.0.0" + cacheable-request "^10.2.8" + decompress-response "^6.0.0" + form-data-encoder "^2.1.2" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" + responselike "^3.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-own-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-2.0.0.tgz#f0f95d58f65804f5d218db32563bb85b8e0417af" + integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hexoid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-2.0.0.tgz#fb36c740ebbf364403fa1ec0c7efd268460ec5b9" + integrity sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-errors@2.0.0, http-errors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http2-wrapper@^2.1.10: + version "2.2.1" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" + integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8" + integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13, ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inspect-with-kind@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz#fce151d4ce89722c82ca8e9860bb96f9167c316c" + integrity sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g== + dependencies: + kind-of "^6.0.2" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + +is-promise@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-stream@^2.0.0, is-stream@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-stream@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-4.0.1.tgz#375cf891e16d2e4baec250b85926cffc14720d9b" + integrity sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +iterare@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + +jackspeak@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" + integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw== + dependencies: + "@isaacs/cliui" "^8.0.2" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3, keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + +lru-cache@^11.0.0: + version "11.0.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39" + integrity sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@0.30.12: + version "0.30.12" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" + integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +magic-string@0.30.17: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +memfs@^3.4.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@^1.1.2, methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.0, micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@^1.28.0, mime-db@^1.53.0: + version "1.53.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" + integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.24: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime-types@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.0.tgz#148453a900475522d095a445355c074cca4f5217" + integrity sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w== + dependencies: + mime-db "^1.53.0" + +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +mimic-response@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" + integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== + +minimatch@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" + integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.3, minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mkdirp@^0.5.4: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multer@1.4.5-lts.1: + version "1.4.5-lts.1" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.5-lts.1.tgz#803e24ad1984f58edffbc79f56e305aec5cfd1ac" + integrity sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ== + dependencies: + append-field "^1.0.0" + busboy "^1.0.0" + concat-stream "^1.5.2" + mkdirp "^0.5.4" + object-assign "^4.1.1" + type-is "^1.6.4" + xtend "^4.0.0" + +mute-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" + integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + +node-emoji@1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.1.tgz#9b7d96af9836577c58f5883e939365fa15623a4a" + integrity sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3: + version "1.13.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" + integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== + +on-finished@2.4.1, on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@1.4.0, once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +ora@5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-cancelable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" + integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@^1.3.3, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" + integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + +path-to-regexp@8.2.0, path-to-regexp@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +peek-readable@^5.3.1: + version "5.4.2" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.4.2.tgz#aff1e1ba27a7d6911ddb103f35252ffc1787af49" + integrity sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +picocolors@^1.0.0, 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@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +piscina@^4.3.1: + version "4.8.0" + resolved "https://registry.yarnpkg.com/piscina/-/piscina-4.8.0.tgz#5f5c5b1f4f3f50f8de894239c98b7b10d41ba4a6" + integrity sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA== + optionalDependencies: + "@napi-rs/nice" "^1.0.1" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pluralize@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +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== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +qs@^6.11.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" + integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.6.3" + unpipe "1.0.0" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readable-stream@^2.2.2: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.1.tgz#bd115327129672dc47f87408f05df9bd9ca3ef55" + integrity sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect-metadata@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-alpn@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" + integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg== + dependencies: + lowercase-keys "^3.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +router@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.0.0.tgz#8692720b95de83876870d7bc638dd3c7e1ae8a27" + integrity sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ== + dependencies: + array-flatten "3.0.0" + is-promise "4.0.0" + methods "~1.1.2" + parseurl "~1.3.3" + path-to-regexp "^8.0.0" + setprototypeof "1.2.0" + utils-merge "1.0.1" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@7.8.1, rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0" + integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +seek-bzip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-2.0.0.tgz#f0478ab6acd0ac72345d18dc7525dd84d3c706a2" + integrity sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg== + dependencies: + commander "^6.0.0" + +semver-regex@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-4.0.5.tgz#fbfa36c7ba70461311f5debcb3928821eb4f9180" + integrity sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw== + +semver-truncate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/semver-truncate/-/semver-truncate-3.0.0.tgz#0e3b4825d4a4225d8ae6e7c72231182b42edba40" + integrity sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg== + dependencies: + semver "^7.3.5" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: + version "7.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.0.tgz#9c6fe61d0c6f9fa9e26575162ee5a9180361b09c" + integrity sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ== + +send@^1.0.0, send@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.1.0.tgz#4efe6ff3bb2139b0e5b2648d8b18d4dec48fc9c5" + integrity sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA== + dependencies: + debug "^4.3.5" + destroy "^1.2.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^0.5.2" + http-errors "^2.0.0" + mime-types "^2.1.35" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" + +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +serve-static@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.1.0.tgz#1b4eacbe93006b79054faa4d6d0a501d7f0e84e2" + integrity sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.0.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.6, side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1, signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@3.0.0, slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +sort-keys-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" + integrity sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw== + dependencies: + sort-keys "^1.0.0" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== + dependencies: + is-plain-obj "^1.0.0" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.5.21, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@0.7.4, source-map@^0.7.3, source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@2.0.1, statuses@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +streamx@^2.15.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.0.tgz#cd7b5e57c95aaef0ff9b2aef7905afa62ec6e4a7" + integrity sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw== + dependencies: + fast-fifo "^1.3.2" + text-decoder "^1.1.0" + optionalDependencies: + bare-events "^2.2.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-3.0.0.tgz#7c9a5d7822ce079a9db40387a4b20d5654746f42" + integrity sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ== + dependencies: + inspect-with-kind "^1.0.5" + is-plain-obj "^1.1.0" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strtok3@^9.0.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-9.1.1.tgz#f8feb188b3fcdbf9b8819cc9211a824c3731df38" + integrity sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^5.3.1" + +superagent@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-9.0.2.tgz#a18799473fc57557289d6b63960610e358bdebc1" + integrity sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.4" + debug "^4.3.4" + fast-safe-stringify "^2.1.1" + form-data "^4.0.0" + formidable "^3.5.1" + methods "^1.1.2" + mime "2.6.0" + qs "^6.11.0" + +supertest@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.0.0.tgz#cac53b3d6872a0b317980b2b0cfa820f09cd7634" + integrity sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA== + dependencies: + methods "^1.1.2" + superagent "^9.0.1" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-observable@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== + +synckit@^0.9.1: + version "0.9.2" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62" + integrity sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + +tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +tar-stream@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" + integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + +terser-webpack-plugin@^5.3.10: + version "5.3.11" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz#93c21f44ca86634257cac176f884f942b7ba3832" + integrity sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser@^5.31.1: + version "5.37.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.37.0.tgz#38aa66d1cfc43d0638fab54e43ff8a4f72a21ba3" + integrity sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-decoder@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" + integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + dependencies: + b4a "^1.6.4" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +token-types@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.0.0.tgz#1ab26be1ef9c434853500c071acfe5c8dd6544a3" + integrity sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-api-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.0.tgz#b9d7d5f7ec9f736f4d0f09758b8607979044a900" + integrity sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ== + +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" + +ts-loader@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.2.tgz#1f3d7f4bb709b487aaa260e8f19b301635d08020" + integrity sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + source-map "^0.7.4" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths-webpack-plugin@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz#f7459a8ed1dd4cf66ad787aefc3d37fff3cf07fc" + integrity sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tapable "^2.2.1" + tsconfig-paths "^4.1.2" + +tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@2.8.1, tslib@^2.1.0, tslib@^2.6.2: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-is@^1.6.4, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type-is@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.0.tgz#7d249c2e2af716665cc149575dadb8b3858653af" + integrity sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +typescript-eslint@^8.20.0: + version "8.22.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.22.0.tgz#1d4becf1d65385e57e9271fbd557ccc22f6c0f53" + integrity sha512-Y2rj210FW1Wb6TWXzQc5+P+EWI9/zdS57hLEc0gnyuvdzWo8+Y8brKlbj0muejonhMI/xAZCnZZwjbIfv1CkOw== + dependencies: + "@typescript-eslint/eslint-plugin" "8.22.0" + "@typescript-eslint/parser" "8.22.0" + "@typescript-eslint/utils" "8.22.0" + +typescript@5.7.3, typescript@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== + +uid@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" + integrity sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== + dependencies: + "@lukeed/csprng" "^1.0.0" + +uint8array-extras@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.4.0.tgz#e42a678a6dd335ec2d21661333ed42f44ae7cc74" + integrity sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ== + +unbzip2-stream@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" + integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +webpack-node-externals@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917" + integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ== + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@5.97.1: + version "5.97.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.97.1.tgz#972a8320a438b56ff0f1d94ade9e82eac155fa58" + integrity sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.6" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.14.0" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@21.1.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yauzl@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.2.0.tgz#7b6cb548f09a48a6177ea0be8ece48deb7da45c0" + integrity sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w== + dependencies: + buffer-crc32 "~0.2.3" + pend "~1.2.0" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yoctocolors-cjs@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" + integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== From e0a84d3962a4af06cef25b29d3119e4844187eae Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 31 Jan 2025 14:41:04 +0100 Subject: [PATCH 02/65] Update package.json and main.ts to use port 2000 for compatibility with kubero v2. --- server-refactored-v3/package.json | 1 + server-refactored-v3/src/main.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index a7c081a0..f747103d 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -11,6 +11,7 @@ "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", + "dev": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index f76bc8d9..42139281 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -3,6 +3,6 @@ import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); - await app.listen(process.env.PORT ?? 3000); + await app.listen(process.env.PORT ?? 2000); // Use port 2000 for compatibility with kubero v2 } bootstrap(); From 5ba11dda77dc9a1e98a7995baed6b5fe1696822c Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 31 Jan 2025 15:05:55 +0100 Subject: [PATCH 03/65] serve static client --- client/vite.config.ts | 2 +- server-refactored-v3/package.json | 1 + server-refactored-v3/src/app.controller.ts | 2 +- server-refactored-v3/src/app.module.ts | 8 +++++++- server-refactored-v3/yarn.lock | 7 +++++++ 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/client/vite.config.ts b/client/vite.config.ts index c40cfdf5..5146eb63 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -54,7 +54,7 @@ export default defineConfig({ extensions: ['.js', '.json', '.jsx', '.mjs', '.ts', '.tsx', '.vue'], }, build: { - outDir: '../server/dist/public', + outDir: '../server-refactored-v3/dist/public', emptyOutDir: true, }, server: { diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index f747103d..04572647 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -24,6 +24,7 @@ "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", + "@nestjs/serve-static": "^5.0.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts index cce879ee..9ece77ce 100644 --- a/server-refactored-v3/src/app.controller.ts +++ b/server-refactored-v3/src/app.controller.ts @@ -5,7 +5,7 @@ import { AppService } from './app.service'; export class AppController { constructor(private readonly appService: AppService) {} - @Get() + @Get('/hello') getHello(): string { return this.appService.getHello(); } diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 86628031..3feb1a5a 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -1,9 +1,15 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { ServeStaticModule } from '@nestjs/serve-static'; +import { join } from 'path'; @Module({ - imports: [], + imports: [ + ServeStaticModule.forRoot({ + rootPath: join(__dirname, '..', 'dist', 'public'), + }), + ], controllers: [AppController], providers: [AppService], }) diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index f19bdb69..a3e5edd3 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -1023,6 +1023,13 @@ jsonc-parser "3.3.1" pluralize "8.0.0" +"@nestjs/serve-static@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@nestjs/serve-static/-/serve-static-5.0.1.tgz#1c57aa706204ea490f00d5f158d92cf4d5d1c9f3" + integrity sha512-s9wtFS9tKR7/Z7X5lVMAIpEGazZN49Ouvy1pNqRLGzI41ameuVWY9vaxZp9oMg83fCcRPMDj8q/6kpwLlosVgg== + dependencies: + path-to-regexp "8.2.0" + "@nestjs/testing@^11.0.1": version "11.0.6" resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-11.0.6.tgz#48840eefffd1455ea74fead5c23f33890ed7577d" From b5675468eec771cde400dc45c51064bc28423e77 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 31 Jan 2025 15:54:12 +0100 Subject: [PATCH 04/65] add and start a websocket --- server-refactored-v3/package.json | 2 + server-refactored-v3/src/app.module.ts | 2 + .../src/events/events.gateway.ts | 27 ++++ .../src/events/events.module.ts | 7 + server-refactored-v3/src/main.ts | 2 + server-refactored-v3/yarn.lock | 124 +++++++++++++++++- 6 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 server-refactored-v3/src/events/events.gateway.ts create mode 100644 server-refactored-v3/src/events/events.module.ts diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index 04572647..e5037045 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -24,7 +24,9 @@ "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", + "@nestjs/platform-socket.io": "^11.0.7", "@nestjs/serve-static": "^5.0.1", + "@nestjs/websockets": "^11.0.7", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 3feb1a5a..05c38a9e 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { EventsModule } from './events/events.module'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; @@ -9,6 +10,7 @@ import { join } from 'path'; ServeStaticModule.forRoot({ rootPath: join(__dirname, '..', 'dist', 'public'), }), + EventsModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/events/events.gateway.ts b/server-refactored-v3/src/events/events.gateway.ts new file mode 100644 index 00000000..fb57f0be --- /dev/null +++ b/server-refactored-v3/src/events/events.gateway.ts @@ -0,0 +1,27 @@ +import { + MessageBody, + SubscribeMessage, + WebSocketGateway, + WebSocketServer, + WsResponse, + } from '@nestjs/websockets'; +import { from, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Server } from 'socket.io'; + +@WebSocketGateway({ + cors: { + origin: '*', + }, +}) + +export class EventsGateway { + @WebSocketServer() + server: Server; + + // TODO: example implementation of a WebSocket event + @SubscribeMessage('events') + findAll(@MessageBody() data: any): Observable> { + return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item }))); + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/events/events.module.ts b/server-refactored-v3/src/events/events.module.ts new file mode 100644 index 00000000..0354dfdb --- /dev/null +++ b/server-refactored-v3/src/events/events.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { EventsGateway } from './events.gateway'; + +@Module({ + providers: [EventsGateway] +}) +export class EventsModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 42139281..0a3ab845 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -4,5 +4,7 @@ import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(process.env.PORT ?? 2000); // Use port 2000 for compatibility with kubero v2 + + console.log(`⚡️[server]: Server is running at: ${await app.getUrl()}`); } bootstrap(); diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index a3e5edd3..4f0bc69b 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -1012,6 +1012,14 @@ path-to-regexp "8.2.0" tslib "2.8.1" +"@nestjs/platform-socket.io@^11.0.7": + version "11.0.7" + resolved "https://registry.yarnpkg.com/@nestjs/platform-socket.io/-/platform-socket.io-11.0.7.tgz#a8cd5af0784b7af7c4a3c3bfd559b0c93421e89c" + integrity sha512-g1j/Uu16w00JtYB00LInELaTkTQBFz93uKvsXGBk2oe27SQv+8XZTqrs7lYBaAjyBsxozQ+PY2Fv+u51yAew9g== + dependencies: + socket.io "4.8.1" + tslib "2.8.1" + "@nestjs/schematics@11.0.0", "@nestjs/schematics@^11.0.0": version "11.0.0" resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-11.0.0.tgz#8e02e86d6515e57eac72923ebae330f57c0ae390" @@ -1037,6 +1045,15 @@ dependencies: tslib "2.8.1" +"@nestjs/websockets@^11.0.7": + version "11.0.7" + resolved "https://registry.yarnpkg.com/@nestjs/websockets/-/websockets-11.0.7.tgz#3e8c06a63abb5e185fda5a187786d9258dd8cc25" + integrity sha512-CFPD+voderUgb48QfaTSWzxHFAih+5mFKFHx4F2SvVwLQmQcAJEwvMd0+UOfIAKB1QZ2gEh3B4SLVqfS9Hr0cw== + dependencies: + iterare "1.2.1" + object-hash "3.0.0" + tslib "2.8.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1099,6 +1116,11 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + "@swc/cli@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@swc/cli/-/cli-0.6.0.tgz#fe986a436797c9d3850938366dbd660c9ba1101f" @@ -1280,6 +1302,13 @@ resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== +"@types/cors@^2.8.12": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + "@types/eslint-scope@^3.7.7": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -1380,7 +1409,7 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== -"@types/node@*", "@types/node@^22.10.7": +"@types/node@*", "@types/node@>=10.0.0", "@types/node@^22.10.7": version "22.12.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.12.0.tgz#bf8af3b2af0837b5a62a368756ff2b705ae0048c" integrity sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA== @@ -1767,6 +1796,14 @@ accepts@^2.0.0: mime-types "^3.0.0" negotiator "^1.0.0" +accepts@~1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -2017,6 +2054,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + bin-version-check@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-5.1.0.tgz#788e80e036a87313f8be7908bc20e5abe43f0837" @@ -2408,6 +2450,11 @@ cookie@0.7.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== +cookie@~0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + cookiejar@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" @@ -2418,7 +2465,7 @@ core-util-is@^1.0.3, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cors@2.8.5: +cors@2.8.5, cors@~2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -2491,6 +2538,13 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3 dependencies: ms "^2.1.3" +debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -2624,6 +2678,26 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== + +engine.io@~6.6.0: + version "6.6.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.4.tgz#0a89a3e6b6c1d4b0c2a2a637495e7c149ec8d8ee" + integrity sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g== + dependencies: + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.7.2" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: version "5.18.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz#91eb1db193896b9801251eeff1c6980278b1e404" @@ -4215,7 +4289,7 @@ mime-db@^1.28.0, mime-db@^1.53.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.24: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -4332,6 +4406,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + negotiator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" @@ -4386,6 +4465,11 @@ object-assign@^4, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-hash@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.13.3: version "1.13.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" @@ -5016,6 +5100,35 @@ slash@3.0.0, slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +socket.io-adapter@~2.5.2: + version "2.5.5" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082" + integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg== + dependencies: + debug "~4.3.4" + ws "~8.17.1" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +socket.io@4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.1.tgz#fa0eaff965cc97fdf4245e8d4794618459f7558a" + integrity sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.6.0" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + sort-keys-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" @@ -5670,6 +5783,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From ba31b7e160a108281f2eefe424c37fba9e729cf4 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sat, 1 Feb 2025 15:48:12 +0100 Subject: [PATCH 05/65] (WIP) add initial auth function --- server-refactored-v3/package.json | 8 ++ server-refactored-v3/src/app.controller.ts | 21 +++- server-refactored-v3/src/app.module.ts | 2 + .../src/auth/auth.controller.spec.ts | 18 +++ .../src/auth/auth.controller.ts | 4 + server-refactored-v3/src/auth/auth.module.ts | 11 ++ .../src/auth/auth.service.spec.ts | 18 +++ server-refactored-v3/src/auth/auth.service.ts | 16 +++ .../src/auth/local.strategy.ts | 19 +++ .../src/users/users.module.ts | 8 ++ .../src/users/users.service.spec.ts | 18 +++ .../src/users/users.service.ts | 19 +++ server-refactored-v3/yarn.lock | 117 +++++++++++++++++- 13 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 server-refactored-v3/src/auth/auth.controller.spec.ts create mode 100644 server-refactored-v3/src/auth/auth.controller.ts create mode 100644 server-refactored-v3/src/auth/auth.module.ts create mode 100644 server-refactored-v3/src/auth/auth.service.spec.ts create mode 100644 server-refactored-v3/src/auth/auth.service.ts create mode 100644 server-refactored-v3/src/auth/local.strategy.ts create mode 100644 server-refactored-v3/src/users/users.module.ts create mode 100644 server-refactored-v3/src/users/users.service.spec.ts create mode 100644 server-refactored-v3/src/users/users.service.ts diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index e5037045..3d8bc8dd 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -23,10 +23,15 @@ "dependencies": { "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/platform-socket.io": "^11.0.7", "@nestjs/serve-static": "^5.0.1", "@nestjs/websockets": "^11.0.7", + "passport": "^0.7.0", + "passport-github2": "^0.1.12", + "passport-local": "^1.0.0", + "passport-oauth2": "^1.8.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, @@ -41,6 +46,9 @@ "@types/express": "^5.0.0", "@types/jest": "^29.5.14", "@types/node": "^22.10.7", + "@types/passport-github2": "^1.2.9", + "@types/passport-local": "^1.0.38", + "@types/passport-oauth2": "^1.4.17", "@types/supertest": "^6.0.2", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts index 9ece77ce..b229dbcd 100644 --- a/server-refactored-v3/src/app.controller.ts +++ b/server-refactored-v3/src/app.controller.ts @@ -1,12 +1,31 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Request, All, Get, Post, UseGuards, HttpStatus, HttpCode, Res } from '@nestjs/common'; +//import { Response } from 'express'; import { AppService } from './app.service'; +import { AuthGuard } from '@nestjs/passport'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} + @UseGuards(AuthGuard('local')) @Get('/hello') getHello(): string { return this.appService.getHello(); } + + @UseGuards(AuthGuard('local')) + @Post('auth/login') + async login(@Request() req) { + return req.user; + } +/* + @All('*') + @HttpCode(404) + catchAll(@Res() res: Response) { + //catchAll() { + res.status(404); + res.json({"statusCode":404,"message":"Not Found"}); + //return '{"statusCode":404,"message":"Not Found"}'; + } +*/ } diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 05c38a9e..093ad7a8 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -3,6 +3,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { EventsModule } from './events/events.module'; import { ServeStaticModule } from '@nestjs/serve-static'; +import { AuthModule } from './auth/auth.module'; import { join } from 'path'; @Module({ @@ -11,6 +12,7 @@ import { join } from 'path'; rootPath: join(__dirname, '..', 'dist', 'public'), }), EventsModule, + AuthModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/auth/auth.controller.spec.ts b/server-refactored-v3/src/auth/auth.controller.spec.ts new file mode 100644 index 00000000..27a31e61 --- /dev/null +++ b/server-refactored-v3/src/auth/auth.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthController } from './auth.controller'; + +describe('AuthController', () => { + let controller: AuthController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AuthController], + }).compile(); + + controller = module.get(AuthController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/auth/auth.controller.ts b/server-refactored-v3/src/auth/auth.controller.ts new file mode 100644 index 00000000..268eeb23 --- /dev/null +++ b/server-refactored-v3/src/auth/auth.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('auth') +export class AuthController {} diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts new file mode 100644 index 00000000..54703c73 --- /dev/null +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { UsersModule } from '../users/users.module'; +import { PassportModule } from '@nestjs/passport'; +import { LocalStrategy } from './local.strategy'; + +@Module({ + imports: [UsersModule, PassportModule], + providers: [AuthService, LocalStrategy], +}) +export class AuthModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/auth.service.spec.ts b/server-refactored-v3/src/auth/auth.service.spec.ts new file mode 100644 index 00000000..800ab662 --- /dev/null +++ b/server-refactored-v3/src/auth/auth.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AuthService], + }).compile(); + + service = module.get(AuthService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/auth/auth.service.ts b/server-refactored-v3/src/auth/auth.service.ts new file mode 100644 index 00000000..3f3f3f93 --- /dev/null +++ b/server-refactored-v3/src/auth/auth.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { UsersService } from '../users/users.service'; + +@Injectable() +export class AuthService { + constructor(private usersService: UsersService) {} + + async validateUser(username: string, pass: string): Promise { + const user = await this.usersService.findOne(username); + if (user && user.password === pass) { + const { password, ...result } = user; + return result; + } + return null; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/local.strategy.ts b/server-refactored-v3/src/auth/local.strategy.ts new file mode 100644 index 00000000..8eb4967f --- /dev/null +++ b/server-refactored-v3/src/auth/local.strategy.ts @@ -0,0 +1,19 @@ +import { Strategy } from 'passport-local'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { AuthService } from './auth.service'; + +@Injectable() +export class LocalStrategy extends PassportStrategy(Strategy) { + constructor(private authService: AuthService) { + super(); + } + + async validate(username: string, password: string): Promise { + const user = await this.authService.validateUser(username, password); + if (!user) { + throw new UnauthorizedException(); + } + return user; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/users/users.module.ts b/server-refactored-v3/src/users/users.module.ts new file mode 100644 index 00000000..416030ce --- /dev/null +++ b/server-refactored-v3/src/users/users.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; + +@Module({ + providers: [UsersService], + exports: [UsersService], +}) +export class UsersModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/users/users.service.spec.ts b/server-refactored-v3/src/users/users.service.spec.ts new file mode 100644 index 00000000..62815ba6 --- /dev/null +++ b/server-refactored-v3/src/users/users.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersService } from './users.service'; + +describe('UsersService', () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersService], + }).compile(); + + service = module.get(UsersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/users/users.service.ts b/server-refactored-v3/src/users/users.service.ts new file mode 100644 index 00000000..a5b34cc7 --- /dev/null +++ b/server-refactored-v3/src/users/users.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; + +// This should be a real class/interface representing a user entity +export type User = any; + +@Injectable() +export class UsersService { + private readonly users = [ + { + userId: 1, + username: 'foo', + password: 'bar', + }, + ]; + + async findOne(username: string): Promise { + return this.users.find(user => user.username === username); + } +} \ No newline at end of file diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 4f0bc69b..98030593 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -1001,6 +1001,11 @@ path-to-regexp "8.2.0" tslib "2.8.1" +"@nestjs/passport@^11.0.5": + version "11.0.5" + resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-11.0.5.tgz#dd3e506c2fb7ddc80fd1321c01cc1a0ca6d6b609" + integrity sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ== + "@nestjs/platform-express@^11.0.1": version "11.0.6" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-11.0.6.tgz#4ab7b81078ab63175db874a939513ddac1f9a08d" @@ -1340,7 +1345,7 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@^5.0.0": +"@types/express@*", "@types/express@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c" integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== @@ -1416,6 +1421,55 @@ dependencies: undici-types "~6.20.0" +"@types/oauth@*": + version "0.9.6" + resolved "https://registry.yarnpkg.com/@types/oauth/-/oauth-0.9.6.tgz#fb5a278f6a826108a7467a01f856324e11e9ba4a" + integrity sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA== + dependencies: + "@types/node" "*" + +"@types/passport-github2@^1.2.9": + version "1.2.9" + resolved "https://registry.yarnpkg.com/@types/passport-github2/-/passport-github2-1.2.9.tgz#7e43b8529276cc8c429ac430f9de06d8406a17da" + integrity sha512-/nMfiPK2E6GKttwBzwj0Wjaot8eHrM57hnWxu52o6becr5/kXlH/4yE2v2rh234WGvSgEEzIII02Nc5oC5xEHA== + dependencies: + "@types/express" "*" + "@types/passport" "*" + "@types/passport-oauth2" "*" + +"@types/passport-local@^1.0.38": + version "1.0.38" + resolved "https://registry.yarnpkg.com/@types/passport-local/-/passport-local-1.0.38.tgz#8073758188645dde3515808999b1c218a6fe7141" + integrity sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg== + dependencies: + "@types/express" "*" + "@types/passport" "*" + "@types/passport-strategy" "*" + +"@types/passport-oauth2@*", "@types/passport-oauth2@^1.4.17": + version "1.4.17" + resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.17.tgz#d5d54339d44f6883d03e69dc0cc0e2114067abb4" + integrity sha512-ODiAHvso6JcWJ6ZkHHroVp05EHGhqQN533PtFNBkg8Fy5mERDqsr030AX81M0D69ZcaMvhF92SRckEk2B0HYYg== + dependencies: + "@types/express" "*" + "@types/oauth" "*" + "@types/passport" "*" + +"@types/passport-strategy@*": + version "0.2.38" + resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.38.tgz#482abba0b165cd4553ec8b748f30b022bd6c04d3" + integrity sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport@*": + version "1.0.17" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.17.tgz#718a8d1f7000ebcf6bbc0853da1bc8c4bc7ea5e6" + integrity sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg== + dependencies: + "@types/express" "*" + "@types/qs@*": version "6.9.18" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2" @@ -2059,6 +2113,11 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== +base64url@3.x.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + bin-version-check@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-5.1.0.tgz#788e80e036a87313f8be7908bc20e5abe43f0837" @@ -4460,6 +4519,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +oauth@0.10.x: + version "0.10.0" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.0.tgz#3551c4c9b95c53ea437e1e21e46b649482339c58" + integrity sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q== + object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4593,6 +4657,45 @@ parseurl@^1.3.3, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-github2@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/passport-github2/-/passport-github2-0.1.12.tgz#a72ebff4fa52a35bc2c71122dcf470d1116f772c" + integrity sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw== + dependencies: + passport-oauth2 "1.x.x" + +passport-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" + integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow== + dependencies: + passport-strategy "1.x.x" + +passport-oauth2@1.x.x, passport-oauth2@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8" + integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== + dependencies: + base64url "3.x.x" + oauth "0.10.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + +passport@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.7.0.tgz#3688415a59a48cf8068417a8a8092d4492ca3a05" + integrity sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + utils-merge "^1.0.1" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -4631,6 +4734,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + peek-readable@^5.3.1: version "5.4.2" resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.4.2.tgz#aff1e1ba27a7d6911ddb103f35252ffc1787af49" @@ -5582,6 +5690,11 @@ typescript@5.7.3, typescript@^5.7.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== +uid2@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44" + integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== + uid@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" @@ -5637,7 +5750,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== From 9a69d4be5ef9d9aaaee79192cc42d8b882b19719 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sat, 1 Feb 2025 15:59:40 +0100 Subject: [PATCH 06/65] (WIP) add basic swagger config --- server-refactored-v3/package.json | 1 + server-refactored-v3/src/main.ts | 16 ++++++++++ server-refactored-v3/yarn.lock | 50 ++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index 3d8bc8dd..acabce83 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -27,6 +27,7 @@ "@nestjs/platform-express": "^11.0.1", "@nestjs/platform-socket.io": "^11.0.7", "@nestjs/serve-static": "^5.0.1", + "@nestjs/swagger": "^11.0.3", "@nestjs/websockets": "^11.0.7", "passport": "^0.7.0", "passport-github2": "^0.1.12", diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 0a3ab845..339500f2 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -1,8 +1,24 @@ import { NestFactory } from '@nestjs/core'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); + + const config = new DocumentBuilder() + .setTitle('Kubero') + .setDescription('Kubero is a web-based tool deploy applications on a Kubernetes clusters. It provides a simple and intuitive interface to manage your clusters, applications, and pipelines.') + .setVersion('3.0') + .addTag('Apps') + .addTag('Addons') + .addTag('Config') + .addTag('Pipeline') + .addTag('Settings') + .build(); + const documentFactory = () => SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api/docs', app, documentFactory); + + await app.listen(process.env.PORT ?? 2000); // Use port 2000 for compatibility with kubero v2 console.log(`⚡️[server]: Server is running at: ${await app.getUrl()}`); diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 98030593..aac5ed44 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -853,6 +853,11 @@ resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== +"@microsoft/tsdoc@0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz#f29a55df17cb6e87cfbabce33ff6a14a9f85076d" + integrity sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA== + "@napi-rs/nice-android-arm-eabi@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz#9a0cba12706ff56500df127d6f4caf28ddb94936" @@ -1001,6 +1006,11 @@ path-to-regexp "8.2.0" tslib "2.8.1" +"@nestjs/mapped-types@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19" + integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw== + "@nestjs/passport@^11.0.5": version "11.0.5" resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-11.0.5.tgz#dd3e506c2fb7ddc80fd1321c01cc1a0ca6d6b609" @@ -1043,6 +1053,18 @@ dependencies: path-to-regexp "8.2.0" +"@nestjs/swagger@^11.0.3": + version "11.0.3" + resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-11.0.3.tgz#27f7d5e7b0835a3430e15c825d4369c1e63a7649" + integrity sha512-oyrhrAzVJz1wYefIYDb6Y0f1VYb8BtYxEI7Ex0ApoUsfGZThyhW9elYANcfBXVaTmICrU8lCESF2ygF6s0ThIw== + dependencies: + "@microsoft/tsdoc" "0.15.0" + "@nestjs/mapped-types" "2.1.0" + js-yaml "4.1.0" + lodash "4.17.21" + path-to-regexp "8.2.0" + swagger-ui-dist "5.18.2" + "@nestjs/testing@^11.0.1": version "11.0.6" resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-11.0.6.tgz#48840eefffd1455ea74fead5c23f33890ed7577d" @@ -1092,6 +1114,11 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@scarf/scarf@=1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" + integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== + "@sec-ant/readable-stream@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" @@ -4097,6 +4124,13 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -4105,13 +4139,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -4225,7 +4252,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.21: +lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5457,6 +5484,13 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swagger-ui-dist@5.18.2: + version "5.18.2" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz#62013074374d272c04ed3030704b88db5aa8c0b7" + integrity sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw== + dependencies: + "@scarf/scarf" "=1.4.0" + symbol-observable@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" From af2623537f2f422fe0d1e4e292a0493c50374324 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sat, 1 Feb 2025 16:12:55 +0100 Subject: [PATCH 07/65] add swagger Server and Auth --- server-refactored-v3/src/main.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 339500f2..652c6dbb 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -9,6 +9,13 @@ async function bootstrap() { .setTitle('Kubero') .setDescription('Kubero is a web-based tool deploy applications on a Kubernetes clusters. It provides a simple and intuitive interface to manage your clusters, applications, and pipelines.') .setVersion('3.0') + .addServer('http://localhost:2000/api', 'Local Development') + .addServer('/api', 'Production') + .addSecurity('bearerAuth', { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }) .addTag('Apps') .addTag('Addons') .addTag('Config') From 75706497697ee0c5c73374ad9f727cc31f823bc5 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sun, 2 Feb 2025 00:48:47 +0100 Subject: [PATCH 08/65] add basic routes --- server-refactored-v3/src/app.controller.ts | 6 ---- server-refactored-v3/src/app.module.ts | 5 +++- .../src/auth/auth.controller.ts | 27 ++++++++++++++++-- server-refactored-v3/src/auth/auth.module.ts | 2 ++ .../src/common/common.controller.spec.ts | 18 ++++++++++++ .../src/common/common.controller.ts | 12 ++++++++ .../src/common/common.module.ts | 9 ++++++ .../src/common/common.service.spec.ts | 18 ++++++++++++ .../src/common/common.service.ts | 27 ++++++++++++++++++ server-refactored-v3/src/main.ts | 28 ++++++++++++++----- 10 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 server-refactored-v3/src/common/common.controller.spec.ts create mode 100644 server-refactored-v3/src/common/common.controller.ts create mode 100644 server-refactored-v3/src/common/common.module.ts create mode 100644 server-refactored-v3/src/common/common.service.spec.ts create mode 100644 server-refactored-v3/src/common/common.service.ts diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts index b229dbcd..d36c6e74 100644 --- a/server-refactored-v3/src/app.controller.ts +++ b/server-refactored-v3/src/app.controller.ts @@ -12,12 +12,6 @@ export class AppController { getHello(): string { return this.appService.getHello(); } - - @UseGuards(AuthGuard('local')) - @Post('auth/login') - async login(@Request() req) { - return req.user; - } /* @All('*') @HttpCode(404) diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 093ad7a8..bf7794d8 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -4,8 +4,10 @@ import { AppService } from './app.service'; import { EventsModule } from './events/events.module'; import { ServeStaticModule } from '@nestjs/serve-static'; import { AuthModule } from './auth/auth.module'; -import { join } from 'path'; +import { CommonModule } from './common/common.module'; + +import { join } from 'path'; @Module({ imports: [ ServeStaticModule.forRoot({ @@ -13,6 +15,7 @@ import { join } from 'path'; }), EventsModule, AuthModule, + CommonModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/auth/auth.controller.ts b/server-refactored-v3/src/auth/auth.controller.ts index 268eeb23..f5140542 100644 --- a/server-refactored-v3/src/auth/auth.controller.ts +++ b/server-refactored-v3/src/auth/auth.controller.ts @@ -1,4 +1,25 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Request, UseGuards, Post, Get, Response } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Controller({ path: 'api/auth', version: '1' }) +export class AuthController { + + @Post('login') + async login(@Request() req) { + return req.user; + } + + @Get('logout') + @UseGuards(AuthGuard('local')) + async logout(@Request() req, @Response() res) { + req.logout({}, function (err: Error) { + if (err) { + throw new Error('Logout failed: Function not implemented.'); + } + res.send("Logged out"); + } as any); + console.log("logged out") + return res.send("logged out"); + } +} -@Controller('auth') -export class AuthController {} diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index 54703c73..63fe0ee9 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -3,9 +3,11 @@ import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; +import { AuthController } from './auth.controller'; @Module({ imports: [UsersModule, PassportModule], providers: [AuthService, LocalStrategy], + controllers: [AuthController], }) export class AuthModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/common/common.controller.spec.ts b/server-refactored-v3/src/common/common.controller.spec.ts new file mode 100644 index 00000000..c2d36fb9 --- /dev/null +++ b/server-refactored-v3/src/common/common.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CommonController } from './common.controller'; + +describe('CommonController', () => { + let controller: CommonController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [CommonController], + }).compile(); + + controller = module.get(CommonController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/common/common.controller.ts b/server-refactored-v3/src/common/common.controller.ts new file mode 100644 index 00000000..1d93509c --- /dev/null +++ b/server-refactored-v3/src/common/common.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { CommonService } from './common.service'; + +@Controller({ path: 'api/', version: '1' }) +export class CommonController { + constructor(private readonly commonService: CommonService) {} + + @Get(['session', 'auth/session']) + getSession(): string { + return this.commonService.getSession(); + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/common/common.module.ts b/server-refactored-v3/src/common/common.module.ts new file mode 100644 index 00000000..d0bb7ce4 --- /dev/null +++ b/server-refactored-v3/src/common/common.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { CommonService } from './common.service'; +import { CommonController } from './common.controller'; + +@Module({ + providers: [CommonService], + controllers: [CommonController] +}) +export class CommonModule {} diff --git a/server-refactored-v3/src/common/common.service.spec.ts b/server-refactored-v3/src/common/common.service.spec.ts new file mode 100644 index 00000000..6ea5a77e --- /dev/null +++ b/server-refactored-v3/src/common/common.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CommonService } from './common.service'; + +describe('CommonService', () => { + let service: CommonService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [CommonService], + }).compile(); + + service = module.get(CommonService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/common/common.service.ts b/server-refactored-v3/src/common/common.service.ts new file mode 100644 index 00000000..31bb89f1 --- /dev/null +++ b/server-refactored-v3/src/common/common.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class CommonService { + getSession(): any { + +/* + let session = { + "isAuthenticated": isAuthenticated, + "version": process.env.npm_package_version, + "kubernetesVersion": req.app.locals.kubero.getKubernetesVersion(), + "operatorVersion": req.app.locals.kubero.getOperatorVersion(), + "buildPipeline": req.app.locals.kubero.getBuildpipelineEnabled(), + "templatesEnabled": req.app.locals.kubero.getTemplateEnabled(), + "auditEnabled": req.app.locals.audit.getAuditEnabled(), + "adminDisabled": req.app.locals.kubero.getAdminDisabled(), + "consoleEnabled": req.app.locals.kubero.getConsoleEnabled(), + "metricsEnabled": req.app.locals.kubero.getMetricsEnabled(), + "sleepEnabled": req.app.locals.kubero.getSleepEnabled(), + } +*/ + let session = { + "isAuthenticated": false, + } + return session; + } +} diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 652c6dbb..715011e6 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -9,18 +9,32 @@ async function bootstrap() { .setTitle('Kubero') .setDescription('Kubero is a web-based tool deploy applications on a Kubernetes clusters. It provides a simple and intuitive interface to manage your clusters, applications, and pipelines.') .setVersion('3.0') - .addServer('http://localhost:2000/api', 'Local Development') - .addServer('/api', 'Production') + .addServer('/', 'Local (default)') + //.addServer('http://localhost:2000/', 'Local') .addSecurity('bearerAuth', { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', }) - .addTag('Apps') - .addTag('Addons') - .addTag('Config') - .addTag('Pipeline') - .addTag('Settings') + .addSecurity('apiKey', { + type: 'apiKey', + name: 'api_key', + in: 'header', + }) + .addSecurity('oauth2', { + type: 'oauth2', + flows: { + implicit: { + authorizationUrl: 'http://example.org/api/oauth/dialog', + scopes: { + 'write:pets': 'modify pets in your account', + 'read:pets': 'read your pets', + }, + }, + }, + }) + //.addSecurityRequirements('bearerAuth') + .build(); const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, documentFactory); From 6f413c19499827ea0b00002bcc16594c61cb7d00 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sun, 2 Feb 2025 21:54:40 +0100 Subject: [PATCH 09/65] add basic modules --- server-refactored-v3/.gitignore | 2 +- server-refactored-v3/package.json | 2 + server-refactored-v3/src/app.module.ts | 24 +- server-refactored-v3/src/apps/apps.module.ts | 4 + .../src/banner/banner.module.ts | 4 + .../src/config/config.module.ts | 4 + .../src/deployments/deployments.module.ts | 4 + server-refactored-v3/src/logs/logs.module.ts | 4 + .../src/metrics/metrics.module.ts | 4 + .../src/pipelines/pipelines.module.ts | 4 + server-refactored-v3/src/repo/repo.module.ts | 4 + .../src/settings/settings.module.ts | 4 + .../src/templates/templates.module.ts | 4 + .../vulnerabilities/vulnerabilities.module.ts | 4 + server-refactored-v3/yarn.lock | 280 +++++++++++++++++- 15 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 server-refactored-v3/src/apps/apps.module.ts create mode 100644 server-refactored-v3/src/banner/banner.module.ts create mode 100644 server-refactored-v3/src/config/config.module.ts create mode 100644 server-refactored-v3/src/deployments/deployments.module.ts create mode 100644 server-refactored-v3/src/logs/logs.module.ts create mode 100644 server-refactored-v3/src/metrics/metrics.module.ts create mode 100644 server-refactored-v3/src/pipelines/pipelines.module.ts create mode 100644 server-refactored-v3/src/repo/repo.module.ts create mode 100644 server-refactored-v3/src/settings/settings.module.ts create mode 100644 server-refactored-v3/src/templates/templates.module.ts create mode 100644 server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts diff --git a/server-refactored-v3/.gitignore b/server-refactored-v3/.gitignore index 4b56acfb..f827f5d8 100644 --- a/server-refactored-v3/.gitignore +++ b/server-refactored-v3/.gitignore @@ -4,7 +4,7 @@ /build # Logs -logs +#logs *.log npm-debug.log* pnpm-debug.log* diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index acabce83..7ba08432 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -21,6 +21,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@kubernetes/client-node": "^1.0.0", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/passport": "^11.0.5", @@ -29,6 +30,7 @@ "@nestjs/serve-static": "^5.0.1", "@nestjs/swagger": "^11.0.3", "@nestjs/websockets": "^11.0.7", + "@otwld/nestjs-kubernetes": "^1.0.3", "passport": "^0.7.0", "passport-github2": "^0.1.12", "passport-local": "^1.0.0", diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index bf7794d8..9729bab9 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -1,3 +1,4 @@ +import { join } from 'path'; import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @@ -5,9 +6,19 @@ import { EventsModule } from './events/events.module'; import { ServeStaticModule } from '@nestjs/serve-static'; import { AuthModule } from './auth/auth.module'; import { CommonModule } from './common/common.module'; +import { AppsModule } from './apps/apps.module'; +import { PipelinesModule } from './pipelines/pipelines.module'; +import { VulnerabilitiesModule } from './vulnerabilities/vulnerabilities.module'; +import { ConfigModule } from './config/config.module'; +import { RepoModule } from './repo/repo.module'; +import { SettingsModule } from './settings/settings.module'; +import { BannerModule } from './banner/banner.module'; +import { TemplatesModule } from './templates/templates.module'; +import { MetricsModule } from './metrics/metrics.module'; +import { LogsModule } from './logs/logs.module'; +import { DeploymentsModule } from './deployments/deployments.module'; -import { join } from 'path'; @Module({ imports: [ ServeStaticModule.forRoot({ @@ -16,6 +27,17 @@ import { join } from 'path'; EventsModule, AuthModule, CommonModule, + AppsModule, + PipelinesModule, + VulnerabilitiesModule, + ConfigModule, + RepoModule, + SettingsModule, + BannerModule, + TemplatesModule, + MetricsModule, + LogsModule, + DeploymentsModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/apps/apps.module.ts b/server-refactored-v3/src/apps/apps.module.ts new file mode 100644 index 00000000..f84eeff0 --- /dev/null +++ b/server-refactored-v3/src/apps/apps.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class AppsModule {} diff --git a/server-refactored-v3/src/banner/banner.module.ts b/server-refactored-v3/src/banner/banner.module.ts new file mode 100644 index 00000000..47d65195 --- /dev/null +++ b/server-refactored-v3/src/banner/banner.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class BannerModule {} diff --git a/server-refactored-v3/src/config/config.module.ts b/server-refactored-v3/src/config/config.module.ts new file mode 100644 index 00000000..19483700 --- /dev/null +++ b/server-refactored-v3/src/config/config.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class ConfigModule {} diff --git a/server-refactored-v3/src/deployments/deployments.module.ts b/server-refactored-v3/src/deployments/deployments.module.ts new file mode 100644 index 00000000..98cc80af --- /dev/null +++ b/server-refactored-v3/src/deployments/deployments.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class DeploymentsModule {} diff --git a/server-refactored-v3/src/logs/logs.module.ts b/server-refactored-v3/src/logs/logs.module.ts new file mode 100644 index 00000000..956d8d10 --- /dev/null +++ b/server-refactored-v3/src/logs/logs.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class LogsModule {} diff --git a/server-refactored-v3/src/metrics/metrics.module.ts b/server-refactored-v3/src/metrics/metrics.module.ts new file mode 100644 index 00000000..b4e50660 --- /dev/null +++ b/server-refactored-v3/src/metrics/metrics.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class MetricsModule {} diff --git a/server-refactored-v3/src/pipelines/pipelines.module.ts b/server-refactored-v3/src/pipelines/pipelines.module.ts new file mode 100644 index 00000000..7acc88a7 --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipelines.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class PipelinesModule {} diff --git a/server-refactored-v3/src/repo/repo.module.ts b/server-refactored-v3/src/repo/repo.module.ts new file mode 100644 index 00000000..8cc04d64 --- /dev/null +++ b/server-refactored-v3/src/repo/repo.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class RepoModule {} diff --git a/server-refactored-v3/src/settings/settings.module.ts b/server-refactored-v3/src/settings/settings.module.ts new file mode 100644 index 00000000..91b37031 --- /dev/null +++ b/server-refactored-v3/src/settings/settings.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class SettingsModule {} diff --git a/server-refactored-v3/src/templates/templates.module.ts b/server-refactored-v3/src/templates/templates.module.ts new file mode 100644 index 00000000..3bca3414 --- /dev/null +++ b/server-refactored-v3/src/templates/templates.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class TemplatesModule {} diff --git a/server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts b/server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts new file mode 100644 index 00000000..a8dd65c9 --- /dev/null +++ b/server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class VulnerabilitiesModule {} diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index aac5ed44..22e8390a 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -592,6 +592,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -848,6 +855,40 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsep-plugin/assignment@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@jsep-plugin/assignment/-/assignment-1.3.0.tgz#fcfc5417a04933f7ceee786e8ab498aa3ce2b242" + integrity sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ== + +"@jsep-plugin/regex@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@jsep-plugin/regex/-/regex-1.0.4.tgz#cb2fc423220fa71c609323b9ba7f7d344a755fcc" + integrity sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg== + +"@kubernetes/client-node@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-1.0.0.tgz#17ee4c7426d47c5da861d4653b24964e476dfb7e" + integrity sha512-a8NSvFDSHKFZ0sR1hbPSf8IDFNJwctEU5RodSCNiq/moRXWmrdmqhb1RRQzF+l+TSBaDgHw3YsYNxxE92STBzw== + dependencies: + "@types/js-yaml" "^4.0.1" + "@types/node" "^22.0.0" + "@types/node-fetch" "^2.6.9" + "@types/stream-buffers" "^3.0.3" + "@types/tar" "^6.1.1" + "@types/ws" "^8.5.4" + form-data "^4.0.0" + isomorphic-ws "^5.0.0" + js-yaml "^4.1.0" + jsonpath-plus "^10.2.0" + node-fetch "^2.6.9" + openid-client "^6.1.3" + rfc4648 "^1.3.0" + stream-buffers "^3.0.2" + tar "^7.0.0" + tmp-promise "^3.0.2" + tslib "^2.5.0" + ws "^8.18.0" + "@lukeed/csprng@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" @@ -985,6 +1026,15 @@ webpack "5.97.1" webpack-node-externals "3.0.0" +"@nestjs/common@^10.0.2": + version "10.4.15" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.15.tgz#27c291466d9100eb86fdbe6f7bbb4d1a6ad55f70" + integrity sha512-vaLg1ZgwhG29BuLDxPA9OAcIlgqzp9/N8iG0wGapyUNTf4IY4O6zAHgN6QalwLhFxq7nOI021vdRojR1oF3bqg== + dependencies: + uid "2.0.2" + iterare "1.2.1" + tslib "2.8.1" + "@nestjs/common@^11.0.1": version "11.0.6" resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.0.6.tgz#ddd534e437e6ef581b06d38cfc9e97a6ed40b14b" @@ -1109,6 +1159,20 @@ dependencies: consola "^3.2.3" +"@otwld/nestjs-kubernetes@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@otwld/nestjs-kubernetes/-/nestjs-kubernetes-1.0.3.tgz#dca3630dc23c2ab1da2f550fc612902ee456da14" + integrity sha512-tVhGZwsptYOgq7J96B77jF+eYlNQ/y7pnl1yAwyvgp9a7cU6zKvQ3PgJnoTEPajP+WY5gQwGyLK301RAN6wqOQ== + dependencies: + "@kubernetes/client-node" "^1.0.0" + "@nestjs/common" "^10.0.2" + tslib "^2.3.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/core@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" @@ -1426,6 +1490,11 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/js-yaml@^4.0.1": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" + integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== + "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1441,6 +1510,14 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/node-fetch@^2.6.9": + version "2.6.12" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" + integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node@*", "@types/node@>=10.0.0", "@types/node@^22.10.7": version "22.12.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.12.0.tgz#bf8af3b2af0837b5a62a368756ff2b705ae0048c" @@ -1448,6 +1525,13 @@ dependencies: undici-types "~6.20.0" +"@types/node@^22.0.0": + version "22.13.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.0.tgz#d376dd9a0ee2f9382d86c2d5d7beb4d198b4ea8c" + integrity sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA== + dependencies: + undici-types "~6.20.0" + "@types/oauth@*": version "0.9.6" resolved "https://registry.yarnpkg.com/@types/oauth/-/oauth-0.9.6.tgz#fb5a278f6a826108a7467a01f856324e11e9ba4a" @@ -1529,6 +1613,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/stream-buffers@^3.0.3": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/stream-buffers/-/stream-buffers-3.0.7.tgz#0b719fa1bd2ca2cc0908205a440e5e569e1aa21e" + integrity sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw== + dependencies: + "@types/node" "*" + "@types/superagent@^8.1.0": version "8.1.9" resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" @@ -1547,6 +1638,21 @@ "@types/methods" "^1.1.4" "@types/superagent" "^8.1.0" +"@types/tar@^6.1.1": + version "6.1.13" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.13.tgz#9b5801c02175344101b4b91086ab2bbc8e93a9b6" + integrity sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw== + dependencies: + "@types/node" "*" + minipass "^4.0.0" + +"@types/ws@^8.5.4": + version "8.5.14" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.14.tgz#93d44b268c9127d96026cf44353725dd9b6c3c21" + integrity sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -2362,6 +2468,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + chrome-trace-event@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" @@ -3394,6 +3505,18 @@ glob@11.0.1: package-json-from-dist "^1.0.0" path-scurry "^2.0.0" +glob@^10.3.7: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -3677,6 +3800,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -3735,6 +3863,15 @@ iterare@1.2.1: resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jackspeak@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" @@ -4119,6 +4256,11 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" +jose@^5.9.6: + version "5.9.6" + resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883" + integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4139,6 +4281,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsep@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.4.0.tgz#19feccbfa51d8a79f72480b4b8e40ce2e17152f0" + integrity sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw== + jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -4188,6 +4335,15 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonpath-plus@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-10.2.0.tgz#84d680544d9868579cc7c8f59bbe153a5aad54c4" + integrity sha512-T9V+8iNYKFL2n2rF+w02LBOT2JjDnTjioaNFrxRy0Bv1y/hNsqR/EBK7Ojy2ythRHwmz2cRIls+9JitQGZC/sw== + dependencies: + "@jsep-plugin/assignment" "^1.3.0" + "@jsep-plugin/regex" "^1.0.4" + jsep "^1.4.0" + keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -4270,6 +4426,11 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^11.0.0: version "11.0.2" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39" @@ -4442,11 +4603,24 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^7.1.2: +minipass@^4.0.0: + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +minizlib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== + dependencies: + minipass "^7.0.4" + rimraf "^5.0.5" + mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -4454,6 +4628,11 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4519,6 +4698,13 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" +node-fetch@^2.6.9: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -4546,6 +4732,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +oauth4webapi@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-3.1.4.tgz#50695385cea8e7a43f3e2e23bc33ea27faece4a7" + integrity sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg== + oauth@0.10.x: version "0.10.0" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.0.tgz#3551c4c9b95c53ea437e1e21e46b649482339c58" @@ -4587,6 +4778,14 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +openid-client@^6.1.3: + version "6.1.7" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-6.1.7.tgz#cb23cbfc1a37690ae553ab72505605d8660da057" + integrity sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg== + dependencies: + jose "^5.9.6" + oauth4webapi "^3.1.4" + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -4743,6 +4942,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" @@ -5033,6 +5240,18 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfc4648@^1.3.0: + version "1.5.4" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.4.tgz#1174c0afba72423a0b70c386ecfeb80aa61b05ca" + integrity sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg== + +rimraf@^5.0.5: + version "5.0.10" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== + dependencies: + glob "^10.3.7" + router@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/router/-/router-2.0.0.tgz#8692720b95de83876870d7bc638dd3c7e1ae8a27" @@ -5321,6 +5540,11 @@ statuses@2.0.1, statuses@^2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +stream-buffers@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.3.tgz#9fc6ae267d9c4df1190a781e011634cac58af3cd" + integrity sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw== + streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" @@ -5518,6 +5742,18 @@ tar-stream@^3.1.7: fast-fifo "^1.2.0" streamx "^2.15.0" +tar@^7.0.0: + version "7.4.3" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" + integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.0.1" + mkdirp "^3.0.1" + yallist "^5.0.0" + terser-webpack-plugin@^5.3.10: version "5.3.11" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz#93c21f44ca86634257cac176f884f942b7ba3832" @@ -5560,6 +5796,13 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tmp-promise@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" + integrity sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== + dependencies: + tmp "^0.2.0" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -5567,6 +5810,11 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -5592,6 +5840,11 @@ token-types@^6.0.0: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tree-kill@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -5666,7 +5919,7 @@ tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.8.1, tslib@^2.1.0, tslib@^2.6.2: +tslib@2.8.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.5.0, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -5830,6 +6083,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webpack-node-externals@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917" @@ -5869,6 +6127,14 @@ webpack@5.97.1: watchpack "^2.4.1" webpack-sources "^3.2.3" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -5930,6 +6196,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + ws@~8.17.1: version "8.17.1" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" @@ -5950,6 +6221,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + yargs-parser@21.1.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" From 613d81af7dba768d4256b57b5c7610173e61eb71 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sun, 2 Feb 2025 22:56:13 +0100 Subject: [PATCH 10/65] init settings module --- server-refactored-v3/src/app.module.ts | 2 - .../src/banner/banner.module.ts | 4 - .../src/settings/settings.controller.spec.ts | 18 +++ .../src/settings/settings.controller.ts | 14 +++ .../src/settings/settings.interface.ts | 110 ++++++++++++++++++ .../src/settings/settings.module.ts | 7 +- .../src/settings/settings.service.spec.ts | 18 +++ .../src/settings/settings.service.ts | 17 +++ server/src/types.ts | 5 + 9 files changed, 188 insertions(+), 7 deletions(-) delete mode 100644 server-refactored-v3/src/banner/banner.module.ts create mode 100644 server-refactored-v3/src/settings/settings.controller.spec.ts create mode 100644 server-refactored-v3/src/settings/settings.controller.ts create mode 100644 server-refactored-v3/src/settings/settings.interface.ts create mode 100644 server-refactored-v3/src/settings/settings.service.spec.ts create mode 100644 server-refactored-v3/src/settings/settings.service.ts diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 9729bab9..3414b844 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -12,7 +12,6 @@ import { VulnerabilitiesModule } from './vulnerabilities/vulnerabilities.module' import { ConfigModule } from './config/config.module'; import { RepoModule } from './repo/repo.module'; import { SettingsModule } from './settings/settings.module'; -import { BannerModule } from './banner/banner.module'; import { TemplatesModule } from './templates/templates.module'; import { MetricsModule } from './metrics/metrics.module'; import { LogsModule } from './logs/logs.module'; @@ -33,7 +32,6 @@ import { DeploymentsModule } from './deployments/deployments.module'; ConfigModule, RepoModule, SettingsModule, - BannerModule, TemplatesModule, MetricsModule, LogsModule, diff --git a/server-refactored-v3/src/banner/banner.module.ts b/server-refactored-v3/src/banner/banner.module.ts deleted file mode 100644 index 47d65195..00000000 --- a/server-refactored-v3/src/banner/banner.module.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Module } from '@nestjs/common'; - -@Module({}) -export class BannerModule {} diff --git a/server-refactored-v3/src/settings/settings.controller.spec.ts b/server-refactored-v3/src/settings/settings.controller.spec.ts new file mode 100644 index 00000000..2e6067c6 --- /dev/null +++ b/server-refactored-v3/src/settings/settings.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SettingsController } from './settings.controller'; + +describe('SettingsController', () => { + let controller: SettingsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SettingsController], + }).compile(); + + controller = module.get(SettingsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts new file mode 100644 index 00000000..d3d93823 --- /dev/null +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get } from '@nestjs/common'; +//import { ApiTags } from '@nestjs/swagger'; +import { SettingsService } from './settings.service'; + +@Controller({ path: 'api/settings', version: '1' }) +@Controller('settings') +export class SettingsController { + constructor(private readonly settingsService: SettingsService) {} + + @Get('/') + async getSettings() { + return this.settingsService.getSettings(); + } +} diff --git a/server-refactored-v3/src/settings/settings.interface.ts b/server-refactored-v3/src/settings/settings.interface.ts new file mode 100644 index 00000000..1784bf39 --- /dev/null +++ b/server-refactored-v3/src/settings/settings.interface.ts @@ -0,0 +1,110 @@ +export interface IKuberoConfig { + podSizeList: IPodSize[]; + buildpacks: IBuildpack[]; + clusterissuer: string; + notifications: INotificationConfig[]; + templates: { + enabled: boolean; + catalogs: [ + { + name: string; + description: string; + index: { + url: string; + format: string; + } + } + ] + } + kubero: { + console: { + enabled: boolean; + } + admin: { + disabled: boolean; + } + readonly: boolean; + banner: { + message: string; + bgcolor: string; + fontcolor: string; + show: boolean; + } + } +} + +interface INotificationConfig{ + enabled: boolean; + name: string; + type: 'slack' | 'webhook' | 'discord', + pipelines: string[], + events: string[], + config: INotificationSlack | INotificationWebhook | INotificationDiscord; +} + +interface INotificationSlack { + url: string; + channel: string; +} + +interface INotificationWebhook { + url: string; + secret: string; +} + +interface INotificationDiscord { + url: string; +} + +interface IPodSize { + name: string; + description: string, + default?: boolean, + active?: boolean, + resources: { + requests?: { + memory: string, + cpu: string + }, + limits?: { + memory: string, + cpu: string + } + } +} + +interface IBuildpack { + name: string; + language: string; + fetch: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext + }, + build: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext + }, + run: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext + }, + tag: string; +} + +interface ISecurityContext { + readOnlyRootFilesystem: boolean; + allowPrivilegeEscalation: boolean; + runAsUser: number; + runAsGroup: number; + runAsNonRoot: boolean; + capabilities: { + drop: string[]; + add: string[]; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/settings/settings.module.ts b/server-refactored-v3/src/settings/settings.module.ts index 91b37031..8f2cf65a 100644 --- a/server-refactored-v3/src/settings/settings.module.ts +++ b/server-refactored-v3/src/settings/settings.module.ts @@ -1,4 +1,9 @@ import { Module } from '@nestjs/common'; +import { SettingsController } from './settings.controller'; +import { SettingsService } from './settings.service'; -@Module({}) +@Module({ + controllers: [SettingsController], + providers: [SettingsService] +}) export class SettingsModule {} diff --git a/server-refactored-v3/src/settings/settings.service.spec.ts b/server-refactored-v3/src/settings/settings.service.spec.ts new file mode 100644 index 00000000..9001518d --- /dev/null +++ b/server-refactored-v3/src/settings/settings.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SettingsService } from './settings.service'; + +describe('SettingsService', () => { + let service: SettingsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SettingsService], + }).compile(); + + service = module.get(SettingsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts new file mode 100644 index 00000000..f2b01b7e --- /dev/null +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { IKuberoConfig } from './settings.interface'; + +@Injectable() +export class SettingsService { + async getSettings(): Promise { + // Load settings from a file or from kubernetes + + let kc: IKuberoConfig = new Object() as IKuberoConfig; + + + // Check if Kubero Administation is disabled + + return kc; + } + +} diff --git a/server/src/types.ts b/server/src/types.ts index 20109cd7..a5ab4aa9 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -357,16 +357,19 @@ export interface IBuildpack { tag: string; } +//Migrated to settings export interface INotificationSlack { url: string; channel: string; } +//Migrated to settings export interface INotificationWebhook { url: string; secret: string; } +//Migrated to settings export interface INotificationDiscord { url: string; } @@ -383,6 +386,7 @@ export interface INotification { message: string; } +//Migrated to settings export interface INotificationConfig{ enabled: boolean; name: string; @@ -392,6 +396,7 @@ export interface INotificationConfig{ config: INotificationSlack | INotificationWebhook | INotificationDiscord; } +//Migrated to settings export interface IKuberoConfig { podSizeList: IPodSize[]; buildpacks: IBuildpack[]; From d27cc36e6f632c018bf4596341154c1bfc9a0957 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Mon, 3 Feb 2025 20:23:24 +0100 Subject: [PATCH 11/65] WIP settings module --- server-refactored-v3/package.json | 3 +- server-refactored-v3/src/main.ts | 10 ++- .../src/settings/buildpack/buildpack.spec.ts | 7 ++ .../src/settings/buildpack/buildpack.ts | 85 +++++++++++++++++++ .../kubero-config/kubero-config.spec.ts | 7 ++ .../settings/kubero-config/kubero-config.ts | 50 +++++++++++ .../src/settings/podsize/podsize.spec.ts | 7 ++ .../src/settings/podsize/podsize.ts | 32 +++++++ .../src/settings/settings.interface.ts | 6 +- .../src/settings/settings.service.ts | 51 +++++++++-- server-refactored-v3/yarn.lock | 5 ++ 11 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 server-refactored-v3/src/settings/buildpack/buildpack.spec.ts create mode 100644 server-refactored-v3/src/settings/buildpack/buildpack.ts create mode 100644 server-refactored-v3/src/settings/kubero-config/kubero-config.spec.ts create mode 100644 server-refactored-v3/src/settings/kubero-config/kubero-config.ts create mode 100644 server-refactored-v3/src/settings/podsize/podsize.spec.ts create mode 100644 server-refactored-v3/src/settings/podsize/podsize.ts diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index 7ba08432..dd6f3261 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -36,7 +36,8 @@ "passport-local": "^1.0.0", "passport-oauth2": "^1.8.0", "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "yaml": "^2.7.0" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 715011e6..17d11b7c 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -1,9 +1,15 @@ import { NestFactory } from '@nestjs/core'; +import { Logger, ConsoleLogger } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule, { + logger: new ConsoleLogger({ + prefix: 'Kubero', + //logLevels: ['log', 'error', 'warn', 'debug', 'verbose'], + }), + }); const config = new DocumentBuilder() .setTitle('Kubero') @@ -42,6 +48,6 @@ async function bootstrap() { await app.listen(process.env.PORT ?? 2000); // Use port 2000 for compatibility with kubero v2 - console.log(`⚡️[server]: Server is running at: ${await app.getUrl()}`); + Logger.warn(`⚡️[server]: Server is running at: ${await app.getUrl()}`, 'Bootstrap'); } bootstrap(); diff --git a/server-refactored-v3/src/settings/buildpack/buildpack.spec.ts b/server-refactored-v3/src/settings/buildpack/buildpack.spec.ts new file mode 100644 index 00000000..998ed859 --- /dev/null +++ b/server-refactored-v3/src/settings/buildpack/buildpack.spec.ts @@ -0,0 +1,7 @@ +import { Buildpack } from './buildpack'; + +describe('Buildpack', () => { + it('should be defined', () => { + expect(new Buildpack()).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/settings/buildpack/buildpack.ts b/server-refactored-v3/src/settings/buildpack/buildpack.ts new file mode 100644 index 00000000..9207663d --- /dev/null +++ b/server-refactored-v3/src/settings/buildpack/buildpack.ts @@ -0,0 +1,85 @@ +import { IBuildpack, ISecurityContext } from '../settings.interface'; + +export class Buildpack implements IBuildpack { + public name: string; + public language: string; + public fetch: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext + }; + public build: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext + }; + public run: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext + }; + public tag: string; + + constructor( + bp: IBuildpack, + ) { + this.name = bp.name; + this.language = bp.language; + this.fetch = bp.fetch; + this.build = bp.build; + this.run = bp.run; + this.tag = bp.tag; + + this.fetch.securityContext = Buildpack.SetSecurityContext(this.fetch.securityContext) + this.build.securityContext = Buildpack.SetSecurityContext(this.build.securityContext) + this.run.securityContext = Buildpack.SetSecurityContext(this.run.securityContext) + + } + + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + public static SetSecurityContext(s: any) : ISecurityContext { + + if (s == undefined) { + return { + runAsUser: 0, + runAsGroup: 0, + //fsGroup: 0, + allowPrivilegeEscalation: false, + readOnlyRootFilesystem: false, + runAsNonRoot: false, + capabilities: { + add: [], + drop: [] + } + } + } + + let securityContext: ISecurityContext = { + runAsUser: s.runAsUser || 0, + runAsGroup: s.runAsGroup || 0, + //fsGroup: s.fsGroup || 0, + allowPrivilegeEscalation: s.allowPrivilegeEscalation || false, + readOnlyRootFilesystem: s.readOnlyRootFilesystem || false, + runAsNonRoot: s.runAsNonRoot || false, + capabilities: s.capabilities || { + add: [], + drop: [] + } + } + + if (securityContext.capabilities.add == undefined) { + securityContext.capabilities.add = [] + } + if (securityContext.capabilities.drop == undefined) { + securityContext.capabilities.drop = [] + } + + return securityContext + } + + +} \ No newline at end of file diff --git a/server-refactored-v3/src/settings/kubero-config/kubero-config.spec.ts b/server-refactored-v3/src/settings/kubero-config/kubero-config.spec.ts new file mode 100644 index 00000000..89c2f92f --- /dev/null +++ b/server-refactored-v3/src/settings/kubero-config/kubero-config.spec.ts @@ -0,0 +1,7 @@ +import { KuberoConfig } from './kubero-config'; + +describe('KuberoConfig', () => { + it('should be defined', () => { + expect(new KuberoConfig()).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/settings/kubero-config/kubero-config.ts b/server-refactored-v3/src/settings/kubero-config/kubero-config.ts new file mode 100644 index 00000000..7ab3a968 --- /dev/null +++ b/server-refactored-v3/src/settings/kubero-config/kubero-config.ts @@ -0,0 +1,50 @@ +import { IKuberoConfig, IPodSize, IBuildpack } from '../settings.interface'; +import { Buildpack } from '../buildpack/buildpack'; +import { PodSize } from '../podsize/podsize'; + +export class KuberoConfig { + public podSizeList: IPodSize[]; + public buildpacks: IBuildpack[]; + public clusterissuer: string; + public templates: { + enabled: boolean; + catalogs: [ + { + name: string; + description: string; + index: { + url: string; + format: string; + } + } + ] + } + public kubero: { + console: { + enabled: boolean; + } + readonly: boolean; + banner: { + message: string; + bgcolor: string; + fontcolor: string; + show: boolean; + } + } + constructor(kc: IKuberoConfig) { + + this.podSizeList = kc.podSizeList; + this.buildpacks = kc.buildpacks; + this.clusterissuer = kc.clusterissuer; + this.templates = kc.templates; + this.kubero = kc.kubero; + + for (let i = 0; i < this.buildpacks.length; i++) { + this.buildpacks[i] = new Buildpack(kc.buildpacks[i]); + } + + for (let i = 0; i < this.podSizeList.length; i++) { + this.podSizeList[i] = new PodSize(kc.podSizeList[i]); + } + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/settings/podsize/podsize.spec.ts b/server-refactored-v3/src/settings/podsize/podsize.spec.ts new file mode 100644 index 00000000..51eb9537 --- /dev/null +++ b/server-refactored-v3/src/settings/podsize/podsize.spec.ts @@ -0,0 +1,7 @@ +import { Podsize } from './podsize'; + +describe('Podsize', () => { + it('should be defined', () => { + expect(new Podsize()).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/settings/podsize/podsize.ts b/server-refactored-v3/src/settings/podsize/podsize.ts new file mode 100644 index 00000000..6b21cc34 --- /dev/null +++ b/server-refactored-v3/src/settings/podsize/podsize.ts @@ -0,0 +1,32 @@ +import { IPodSize } from "../settings.interface"; + +export class PodSize implements IPodSize { + public name: string; + public description: string; + public default?: boolean | undefined; + public resources: { + requests?: { + memory: string; + cpu: string; + } | undefined; + limits?: { + memory: string; + cpu: string; + } | undefined; + }; + constructor(ps: IPodSize) { + this.name = ps.name; + this.description = ps.description; + this.default = ps.default; + this.resources = { + requests: { + memory: ps.resources.requests?.memory || "", + cpu: ps.resources.requests?.cpu || "" + }, + limits: { + memory: ps.resources.limits?.memory || "", + cpu: ps.resources.limits?.cpu || "" + } + } + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/settings/settings.interface.ts b/server-refactored-v3/src/settings/settings.interface.ts index 1784bf39..3a53b854 100644 --- a/server-refactored-v3/src/settings/settings.interface.ts +++ b/server-refactored-v3/src/settings/settings.interface.ts @@ -56,7 +56,7 @@ interface INotificationDiscord { url: string; } -interface IPodSize { +export interface IPodSize { name: string; description: string, default?: boolean, @@ -73,7 +73,7 @@ interface IPodSize { } } -interface IBuildpack { +export interface IBuildpack { name: string; language: string; fetch: { @@ -97,7 +97,7 @@ interface IBuildpack { tag: string; } -interface ISecurityContext { +export interface ISecurityContext { readOnlyRootFilesystem: boolean; allowPrivilegeEscalation: boolean; runAsUser: number; diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index f2b01b7e..8c26300c 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -1,17 +1,56 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { IKuberoConfig } from './settings.interface'; +import { KuberoConfig } from './kubero-config/kubero-config'; +import { readFileSync, writeFileSync } from 'fs'; +import YAML from 'yaml' +import { join } from 'path'; @Injectable() export class SettingsService { - async getSettings(): Promise { - // Load settings from a file or from kubernetes + private readonly logger = new Logger(SettingsService.name); - let kc: IKuberoConfig = new Object() as IKuberoConfig; + // Load settings from a file or from kubernetes + async getSettings(): Promise { + // TODO: Check if Kubero Administation is disabled - // Check if Kubero Administation is disabled + let configMap: KuberoConfig + if (process.env.NODE_ENV === "production") { + configMap = new KuberoConfig(this.loadConfigFromKubernetes()) + } else { + configMap = new KuberoConfig(this.readConfig()) + } - return kc; + return configMap; + } + + private loadConfigFromKubernetes(): IKuberoConfig { + // TODO: Load config from kubernetes + return new Object() as IKuberoConfig + } + + private readConfig(): IKuberoConfig { + // read config from local filesystem (dev mode) + //const path = join(__dirname, 'config.yaml') + const path = process.env.KUBERO_CONFIG_PATH || join(__dirname, 'config.yaml') + let settings: string + try { + settings = readFileSync( path, 'utf8') + return YAML.parse(settings) as IKuberoConfig + } catch (e) { + this.logger.error('Error reading config file') + + return new Object() as IKuberoConfig + } } + // write config to local filesystem (dev mode) + private writeConfig(configMap: KuberoConfig) { + const path = process.env.KUBERO_CONFIG_PATH || join(__dirname, 'config.yaml') + writeFileSync(path, YAML.stringify(configMap), { + flag: 'w', + encoding: 'utf8' + }); + } + } diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 22e8390a..035dba5a 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -6226,6 +6226,11 @@ yallist@^5.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== +yaml@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98" + integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA== + yargs-parser@21.1.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" From 0d61eaef20e62af3c817ab75e3ebeaba440587fc Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Mon, 3 Feb 2025 20:23:50 +0100 Subject: [PATCH 12/65] mark migrated types --- server/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/types.ts b/server/src/types.ts index a5ab4aa9..a02d6446 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -179,6 +179,7 @@ export interface ITemplate { addons: IAddon[] } +//Migrated to settings export interface ISecurityContext { readOnlyRootFilesystem: boolean; allowPrivilegeEscalation: boolean; From 87fad09f1c49b0babccf957bd383e9b1fe0d172e Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Tue, 4 Feb 2025 08:22:55 +0100 Subject: [PATCH 13/65] migrate templates, pipeline, apps and kubectl --- server-refactored-v3/package.json | 5 +- .../src/addons/addons.interface.ts | 58 + server-refactored-v3/src/apps/app/app.spec.ts | 8 + server-refactored-v3/src/apps/app/app.ts | 264 ++++ .../src/apps/apps.interface.ts | 174 +++ .../src/kubectl/kubectl.interface.ts | 50 + .../src/kubectl/kubectl.spec.ts | 7 + server-refactored-v3/src/kubectl/kubectl.ts | 1267 +++++++++++++++++ .../src/pipelines/pipeline/pipeline.spec.ts | 98 ++ .../src/pipelines/pipeline/pipeline.ts | 58 + .../src/pipelines/pipelines.interface.ts | 54 + .../src/templates/template.spec.ts | 7 + .../src/templates/template.ts | 111 ++ .../src/templates/templates.interface.ts | 48 + server-refactored-v3/yarn.lock | 764 +++++++--- server/src/types.ts | 11 +- 16 files changed, 2766 insertions(+), 218 deletions(-) create mode 100644 server-refactored-v3/src/addons/addons.interface.ts create mode 100644 server-refactored-v3/src/apps/app/app.spec.ts create mode 100644 server-refactored-v3/src/apps/app/app.ts create mode 100644 server-refactored-v3/src/apps/apps.interface.ts create mode 100644 server-refactored-v3/src/kubectl/kubectl.interface.ts create mode 100644 server-refactored-v3/src/kubectl/kubectl.spec.ts create mode 100644 server-refactored-v3/src/kubectl/kubectl.ts create mode 100644 server-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts create mode 100644 server-refactored-v3/src/pipelines/pipeline/pipeline.ts create mode 100644 server-refactored-v3/src/pipelines/pipelines.interface.ts create mode 100644 server-refactored-v3/src/templates/template.spec.ts create mode 100644 server-refactored-v3/src/templates/template.ts create mode 100644 server-refactored-v3/src/templates/templates.interface.ts diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index dd6f3261..84c200e5 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -21,7 +21,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@kubernetes/client-node": "^1.0.0", + "@kubernetes/client-node": "^0.20.0", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/passport": "^11.0.5", @@ -30,7 +30,8 @@ "@nestjs/serve-static": "^5.0.1", "@nestjs/swagger": "^11.0.3", "@nestjs/websockets": "^11.0.7", - "@otwld/nestjs-kubernetes": "^1.0.3", + "@types/bcrypt": "^5.0.2", + "bcrypt": "^5.1.1", "passport": "^0.7.0", "passport-github2": "^0.1.12", "passport-local": "^1.0.0", diff --git a/server-refactored-v3/src/addons/addons.interface.ts b/server-refactored-v3/src/addons/addons.interface.ts new file mode 100644 index 00000000..8a40360f --- /dev/null +++ b/server-refactored-v3/src/addons/addons.interface.ts @@ -0,0 +1,58 @@ + +import { KubernetesListObject, KubernetesObject } from '@kubernetes/client-node' + +export interface IAddon { + id: string + operator: string, + enabled: boolean, + name: string, + CRDkind: string, + icon: string, + displayName: string, + version: string + plural: string; + description?: string, + install: string, + formfields: {[key: string]: IAddonFormFields}, + crd: KubernetesObject +} + +interface IAddonMinimal { + group: string; + version: string; + namespace: string; + pipeline: string; + phase: string; + plural: string; + id: string; +} + +interface IAddonFormFields { + type: 'text' | 'number' |'switch', + label: string, + name: string, + required: boolean, + default: string | number | boolean, + description?: string, + //value?: string | number | boolean, +} + +export interface IAddon { + id: string + operator: string, + enabled: boolean, + name: string, + CRDkind: string, + icon: string, + displayName: string, + version: string + plural: string; + description?: string, + install: string, + formfields: {[key: string]: IAddonFormFields}, + crd: KubernetesObject +} + +interface IUniqueAddons { + [key: string]: IAddon +} \ No newline at end of file diff --git a/server-refactored-v3/src/apps/app/app.spec.ts b/server-refactored-v3/src/apps/app/app.spec.ts new file mode 100644 index 00000000..5add7334 --- /dev/null +++ b/server-refactored-v3/src/apps/app/app.spec.ts @@ -0,0 +1,8 @@ +import { App } from './app'; +import { IApp } from '../apps.interface'; + +describe('App', () => { + it('should be defined', () => { + expect(new App({} as IApp)).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/apps/app/app.ts b/server-refactored-v3/src/apps/app/app.ts new file mode 100644 index 00000000..f5e12910 --- /dev/null +++ b/server-refactored-v3/src/apps/app/app.ts @@ -0,0 +1,264 @@ +import { + IApp, + IGithubRepository, + ICronjob, + IExtraVolume, +} from '../apps.interface'; + +import { IKubectlMetadata, IKubectlApp } from "../../kubectl/kubectl.interface"; +import { IAddon } from '../../addons/addons.interface'; +import { ISecurityContext, IPodSize } from "../../settings/settings.interface" +import { hashSync, genSaltSync } from 'bcrypt'; +import { Buildpack } from '../../settings/buildpack/buildpack'; + +export class KubectlApp implements IKubectlApp{ + apiVersion: string; + kind: string; + metadata: IKubectlMetadata; + spec: App; + + constructor(app: App) { + this.apiVersion = "application.kubero.dev/v1alpha1"; + this.kind = "KuberoApp"; + this.metadata = { + name: app.name, + labels: { + manager: 'kubero', + } + } + this.spec = app; + } +} + +export class App implements IApp{ + public name: string + public pipeline: string + public phase: string + public sleep: string + public buildpack: string + public deploymentstrategy: 'git' | 'docker'; + public buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; + public gitrepo?: IGithubRepository + public branch: string + public autodeploy: boolean + public podsize: IPodSize + public autoscale: boolean + //public envVars: {[key: string]: string} = {} + public basicAuth: { + enabled: boolean; + realm: string; + accounts: { + user: string; + pass: string; + hash?: string; + }[]; + }; + public envVars: {}[] = [] + public extraVolumes: IExtraVolume[] = [] + public cronjobs: ICronjob[] = [] + public addons: IAddon[] = [] + + public web: { + replicaCount: number + autoscaling: { + minReplicas: number + maxReplicas: number + targetCPUUtilizationPercentage?: number + targetMemoryUtilizationPercentage?: number + } + } + + public worker: { + replicaCount: number + autoscaling: { + minReplicas: number + maxReplicas: number + targetCPUUtilizationPercentage?: number + targetMemoryUtilizationPercentage?: number + } + } + + private affinity: {}; + private autoscaling: { + enabled: boolean, + }; + private fullnameOverride: ""; + + public image: { + containerPort: number, + pullPolicy: 'Always', + repository: string, + tag: string, + command: [string], + fetch: { + repository: string, + tag: string, + securityContext?: ISecurityContext + } + build: { + repository: string, + tag: string, + securityContext?: ISecurityContext + } + run: { + repository: string, + tag: string, + readOnlyAppStorage?: boolean, + securityContext: ISecurityContext + } + }; + + public vulnerabilityscan: { + enabled: boolean + schedule: string + image: { + repository: string + tag: string + } + } + + private imagePullSecrets: []; + public ingress: { + annotations: Object, + className: string, + enabled: boolean, + hosts: [ + { + host: string, + paths: [ + {path: string, pathType: string} + ] + } + ], + tls: [ + { + hosts: string[], + secretName: string + } + ] | [] + }; + private nameOverride: ""; + private nodeSelector: {}; + private podAnnotations: {}; + private podSecurityContext: {}; + private replicaCount: 1; + public resources: {}; + private service: { + port: 80, + type: 'ClusterIP' + }; + public serviceAccount: { + annotations: Object, + create: boolean, + name: string, + }; + private tolerations: []; + + public healthcheck: { + enabled: boolean, + path: string, + startupSeconds: number, + timeoutSeconds: number, + periodSeconds: number, + }; + + constructor( + app: IApp + ) { + this.name = app.name + this.pipeline = app.pipeline + this.phase = app.phase + this.sleep = app.sleep + this.buildpack = app.buildpack + this.deploymentstrategy = app.deploymentstrategy + this.buildstrategy = app.buildstrategy + this.gitrepo = app.gitrepo + this.branch = app.branch + this.autodeploy = app.autodeploy + this.podsize = app.podsize + this.autoscale = app.autoscale // TODO: may be redundant with autoscaling.enabled + + const salt = genSaltSync(10); + if (app.basicAuth !== undefined) { + this.basicAuth = { + realm: app.basicAuth.realm, + enabled: app.basicAuth.enabled, + accounts: app.basicAuth.accounts.map(account => { + return { + user: account.user, + pass: account.pass, + // generate hash with bcrypt from user and pass + //hash: account.user+':$5$'+crypto.createHash('sha256').update(account.user+account.pass).digest('base64') + //hash: account.user+':{SHA}'+crypto.createHash('sha1').update(account.pass).digest('base64') // works + hash: account.user+':'+hashSync(account.pass, salt) + } + }) + } + } else { + this.basicAuth = { + realm: 'Authenticate', + enabled: false, + accounts: [] + } + } + + this.envVars = app.envVars + + this.serviceAccount = app.serviceAccount; + + this.extraVolumes = app.extraVolumes + + this.cronjobs = app.cronjobs + + this.addons = app.addons + + this.web = app.web + this.worker = app.worker + + this.affinity = {}; + this.autoscaling = { + enabled: app.autoscale + } + this.fullnameOverride = "", + + this.image = { + containerPort: app.image.containerPort, + pullPolicy: 'Always', + repository: app.image.repository || 'ghcr.io/kubero-dev/idler', + tag: app.image.tag || 'v1', + command: app.image.command, + fetch: app.image.fetch, + build: app.image.build, + run: app.image.run, + } + + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + this.image.fetch.securityContext = Buildpack.SetSecurityContext(this.image.fetch.securityContext) + this.image.build.securityContext = Buildpack.SetSecurityContext(this.image.build.securityContext) + this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) + + this.vulnerabilityscan = app.vulnerabilityscan + + this.imagePullSecrets = [] + + this.ingress = app.ingress + this.ingress.className = app.ingress.className || process.env.KUBERNETES_INGRESS_CLASSNAME || "nginx" + this.ingress.enabled = true + + this.nameOverride= "", + this.nodeSelector= {}, + this.podAnnotations= {}, + this.podSecurityContext= {}, + this.replicaCount= 1, + this.resources= app.podsize.resources, + this.service= { + port: 80, + type: 'ClusterIP' + }, + this.tolerations= [] + + this.healthcheck = app.healthcheck + } +} + diff --git a/server-refactored-v3/src/apps/apps.interface.ts b/server-refactored-v3/src/apps/apps.interface.ts new file mode 100644 index 00000000..6dd10ce7 --- /dev/null +++ b/server-refactored-v3/src/apps/apps.interface.ts @@ -0,0 +1,174 @@ +import { IAddon } from "../addons/addons.interface" +import { IPodSize, ISecurityContext } from "../settings/settings.interface" +import { IKubectlMetadata } from "../kubectl/kubectl.interface" + +export interface IApp { + name: string, + pipeline: string, + phase: string, + sleep: string, + buildpack: string, + deploymentstrategy: 'git' | 'docker', + buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks', + gitrepo?: IGithubRepository, + branch: string, + autodeploy: boolean, + podsize: IPodSize, + autoscale: boolean, + basicAuth: { + enabled: boolean, + realm: string, + accounts: { + user: string, + pass: string, + hash?: string, + }[] + }, + envVars: {}[], + image : { + repository: string, + tag: string, + command: [string], + pullPolicy: 'Always', + containerPort: number, + fetch: { + repository: string, + tag: string, + securityContext?: ISecurityContext + } + build: { + repository: string, + tag: string, + securityContext?: ISecurityContext + } + run: { + repository: string, + readOnlyAppStorage?: boolean, + tag: string, + readOnly?: boolean, + securityContext: ISecurityContext + } + } + + web: { + replicaCount: number + autoscaling: { + minReplicas: number + maxReplicas: number + targetCPUUtilizationPercentage?: number + targetMemoryUtilizationPercentage?: number + } + } + + worker: { + replicaCount: number + autoscaling: { + minReplicas: number + maxReplicas: number + targetCPUUtilizationPercentage?: number + targetMemoryUtilizationPercentage?: number + } + } + + extraVolumes: IExtraVolume[], + cronjobs: ICronjob[] + addons: IAddon[] + vulnerabilityscan: { + enabled: boolean + schedule: string + image: { + repository: string + tag: string + } + } + ingress: { + annotations: Object, + className: string, + enabled: boolean, + hosts: [ + { + host: string + paths: [ + {path: string, pathType: string} + ] + } + ], + tls: [ + { + hosts: string[], + secretName: string + } + ] | [] + }, +/* + affinity: {}, + fullnameOverride: string, + imagePullSecrets: [], + ingress?: { + annotations: {}, + className: string, + enabled: boolean, + hosts: [ + {host: string} + ], + paths: [ + {path: string, pathType: string} + ], + tls: [], + }, + nameOverride: string, + nodeSelector: {}, + podAnnotations: {}, + podSecurityContext: {}, + replicaCount: number, +*/ + resources: {}, +/* + service: { + port: number, + type: string + }, + */ + serviceAccount: { + annotations: {}, + create: boolean, + name: string, + }, + //tolerations: [], + healthcheck: { + enabled: boolean, + path: string, + startupSeconds: number, + timeoutSeconds: number, + periodSeconds: number, + }, +} + +export interface IExtraVolume { + name: string, + mountPath: string, + emptyDir: boolean, + size: string, + storageClass: string, + accessModes: string[], +} + +export interface IGithubRepository { + admin: boolean, + description?: string, + id?: number, + name?: string, + node_id?: string, + owner?: string, + private?: boolean, + ssh_url?: string + clone_url?: string, +} + +export interface ICronjob { + name: string, + schedule: string, + command: [string], + image: string, + imagePullPolicy: string, +} \ No newline at end of file diff --git a/server-refactored-v3/src/kubectl/kubectl.interface.ts b/server-refactored-v3/src/kubectl/kubectl.interface.ts new file mode 100644 index 00000000..6a39ef89 --- /dev/null +++ b/server-refactored-v3/src/kubectl/kubectl.interface.ts @@ -0,0 +1,50 @@ +import { IApp } from 'src/apps/apps.interface'; +import { IPipeline } from '../pipelines/pipelines.interface'; +import { Template } from '../templates/template'; + +export interface IKubectlPipelineList { + apiVersion: string; + kind: string; + metadata: IKubectlMetadata, + items: IKubectlPipeline[] +} + +export interface IKubectlPipeline { + apiVersion: string; + kind: string; + metadata: IKubectlMetadata, + spec: IPipeline +} + +export interface IKubectlMetadata { + creationTimestamp?: Date; + generation?: number; + //labels?: [Object]; + annotations?: Object; + labels?: { + 'kubernetes.io/metadata.name'?: String, + manager?: string; + } + managedFields?: [Array: Object]; + name?: String; + namespace?: string; + resourceVersion?: string; + uid?: string; + finalizers?: [Array: Object]; +} + +export interface IKubectlAppList { + apiVersion: string; + items: IKubectlApp []; + kind: string; + metadata: { continue: string; resourceVersion: string; } +} + +export interface IKubectlApp +{ + apiVersion: string; + kind: string; + metadata: IKubectlMetadata + spec: IApp ; +} + diff --git a/server-refactored-v3/src/kubectl/kubectl.spec.ts b/server-refactored-v3/src/kubectl/kubectl.spec.ts new file mode 100644 index 00000000..6aeb7133 --- /dev/null +++ b/server-refactored-v3/src/kubectl/kubectl.spec.ts @@ -0,0 +1,7 @@ +import { Kubectl } from './kubectl'; + +describe('Kubectl', () => { + it('should be defined', () => { + expect(new Kubectl()).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/kubectl/kubectl.ts b/server-refactored-v3/src/kubectl/kubectl.ts new file mode 100644 index 00000000..15becd14 --- /dev/null +++ b/server-refactored-v3/src/kubectl/kubectl.ts @@ -0,0 +1,1267 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList} from './kubectl.interface'; +import { IPipeline, } from '../pipelines/pipelines.interface'; +import { KubectlPipeline } from '../pipelines/pipeline/pipeline'; +import { KubectlApp, App } from '../apps/app/app'; + +import { + KubeConfig, + Exec, + VersionApi, + CoreV1Api, + AppsV1Api, + CustomObjectsApi, + KubernetesListObject, + KubernetesObject, + VersionInfo, + PatchUtils, + Log as KubeLog, + V1Pod, + CoreV1Event, + CoreV1EventList, + V1ConfigMap, + V1Namespace, + Metrics, + PodMetric, + PodMetricsList, + NodeMetric, + StorageV1Api, + BatchV1Api, + NetworkingV1Api, + V1ServiceAccount, + V1Job +} from '@kubernetes/client-node' +import { WebSocket } from 'ws'; +import stream from 'stream'; +import internal from 'stream'; + +@Injectable() +export class Kubectl { + private kc: KubeConfig; + private versionApi: VersionApi = {} as VersionApi; + private coreV1Api: CoreV1Api = {} as CoreV1Api; + private appsV1Api: AppsV1Api = {} as AppsV1Api; + private metricsApi: Metrics = {} as Metrics; + private storageV1Api: StorageV1Api = {} as StorageV1Api; + private batchV1Api: BatchV1Api = {} as BatchV1Api; + private customObjectsApi: CustomObjectsApi = {} as CustomObjectsApi; + private networkingV1Api: NetworkingV1Api = {} as NetworkingV1Api; + public kubeVersion: VersionInfo | void; + public kuberoOperatorVersion: string | undefined; + private patchUtils: PatchUtils = {} as PatchUtils; + public log: KubeLog; + //public config: IKuberoConfig; + private exec: Exec = {} as Exec; + private readonly logger = new Logger(Kubectl.name); + + constructor() { + 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) { + let buff = Buffer.from(process.env.KUBECONFIG_BASE64, 'base64'); + const kubeconfig = buff.toString('ascii'); + this.kc.loadFromString(kubeconfig); + + this.logger.debug("ℹ️ Kubeconfig loaded from base64"); + } else if(process.env.KUBECONFIG_PATH) { + this.kc.loadFromFile(process.env.KUBECONFIG_PATH); + this.logger.debug("ℹ️ Kubeconfig loaded from file " + process.env.KUBECONFIG_PATH); + } else{ + try { + this.kc.loadFromCluster(); + this.logger.debug("ℹ️ Kubeconfig loaded from cluster"); + } catch (error) { + this.logger.debug("❌ Error loading from cluster"); + //this.logger.debug(error); + } + } + + + try { + this.versionApi = this.kc.makeApiClient(VersionApi); + this.coreV1Api = this.kc.makeApiClient(CoreV1Api); + this.appsV1Api = this.kc.makeApiClient(AppsV1Api); + this.storageV1Api = this.kc.makeApiClient(StorageV1Api); + this.batchV1Api = this.kc.makeApiClient(BatchV1Api); + this.networkingV1Api = this.kc.makeApiClient(NetworkingV1Api); + this.metricsApi = new Metrics(this.kc); + this.patchUtils = new PatchUtils(); + this.exec = new Exec(this.kc) + this.customObjectsApi = this.kc.makeApiClient(CustomObjectsApi); + } catch (error) { + console.log("❌ Error creating api clients. Check kubeconfig, cluster connectivity and context"); + //this.logger.debug(error); + this.kubeVersion = void 0; + return; + } + + 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 => { + console.log("❌ Failed to get Kubernetes version"); + //this.logger.debug(error); + }); + + this.getOperatorVersion() + .then(v => { + this.logger.debug("ℹ️ 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)); + return versionInfo.body; + } catch (error) { + this.logger.debug("getKubeVersion: error getting kube version"); + //this.logger.debug(error); + } + } + + private async getOperatorVersion(): Promise { + const contextName = this.getCurrentContext(); + const namespace = "kubero-operator-system"; + + if (contextName) { + const pods = await this.getPods(namespace, contextName) + .catch(error => { + this.logger.debug("❌ Failed to get Operator Version"); + //this.logger.debug(error); + //return 'error'; + }); + if (pods) { + for (const pod of pods) { + if (pod?.metadata?.name?.startsWith('kubero-operator-controller-manager')) { + const container = pod?.spec?.containers.filter((c: any) => c.name == 'manager')[0]; + return container?.image?.split(':')[1] || 'unknown'; + } + } + }else{ + return 'error getting operator version'; + } + } + } + + public getContexts() { + return this.kc.getContexts() + } + + public async setCurrentContext(context: string) { + this.kc.setCurrentContext(context) + } + + public getCurrentContext() { + return this.kc.getCurrentContext() + } + + public async getNamespaces(): Promise { + const namespaces = await this.coreV1Api.listNamespace(); + return namespaces.body.items; + } + + public async getPipelinesList() { + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + try { + let pipelines = await this.customObjectsApi.listNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + process.env.KUBERO_NAMESPACE || 'kubero', + 'kuberopipelines' + ) + return pipelines.body as IKubectlPipelineList; + } catch (error) { + //this.logger.debug(error); + this.logger.debug("❌ getPipelinesList: error getting pipelines"); + } + const pipelines = {} as IKubectlPipelineList; + pipelines.items = []; + return pipelines; + } + + public async createPipeline(pl: IPipeline) { + this.logger.debug("create pipeline: " + pl.name); + let pipeline = new KubectlPipeline(pl); + + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + await this.customObjectsApi.createNamespacedCustomObject( + "application.kubero.dev", + "v1alpha1", + process.env.KUBERO_NAMESPACE || 'kubero', + "kuberopipelines", + pipeline + ).catch(error => { + this.logger.debug("❌ Error creating pipeline: " + pl.name); + //this.logger.debug(error); + }); + } + + public async updatePipeline(pl: IPipeline, resourceVersion: string ) { + this.logger.debug("update pipeline: " + pl.name); + let pipeline = new KubectlPipeline(pl); + pipeline.metadata.resourceVersion = resourceVersion; + + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + await this.customObjectsApi.replaceNamespacedCustomObject( + "application.kubero.dev", + "v1alpha1", + process.env.KUBERO_NAMESPACE || 'kubero', + "kuberopipelines", + pl.name, + pipeline + ).catch(error => { + this.logger.debug("❌ Error updating pipeline: " + pl.name); + //this.logger.debug(error); + }); + } + + public async deletePipeline(pipelineName: string) { + this.logger.debug("delete pipeline: " + pipelineName); + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + await this.customObjectsApi.deleteNamespacedCustomObject( + "application.kubero.dev", + "v1alpha1", + process.env.KUBERO_NAMESPACE || 'kubero', + "kuberopipelines", + pipelineName + ).catch(error => { + this.logger.debug(error); + }); + } + + public async getPipeline(pipelineName: string): Promise { + + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + let pipeline = await this.customObjectsApi.getNamespacedCustomObject( + "application.kubero.dev", + "v1alpha1", + process.env.KUBERO_NAMESPACE || 'kubero', + "kuberopipelines", + pipelineName + ).catch(error => { + //this.logger.debug(error); + this.logger.debug("getPipeline: error getting pipeline"); + throw error; + }); + if (pipeline) { + return pipeline.body as IKubectlPipeline; + } else { + throw new Error("Pipeline not found"); + //return {} as IKubectlPipeline; + } + } + + public async createApp(app: App, context: string) { + this.logger.debug("create app: " + app.name); + this.kc.setCurrentContext(context); + + let appl = new KubectlApp(app); + + let namespace = app.pipeline+'-'+app.phase; + + await this.customObjectsApi.createNamespacedCustomObject( + "application.kubero.dev", + "v1alpha1", + namespace, + "kuberoapps", + appl + ).catch(error => { + console.log(error); + }) + } + + public async updateApp(app: App, resourceVersion: string, context: string) { + this.logger.debug("update app: " + app.name); + this.kc.setCurrentContext(context); + + let appl = new KubectlApp(app); + appl.metadata.resourceVersion = resourceVersion; + + let namespace = app.pipeline+'-'+app.phase; + + await this.customObjectsApi.replaceNamespacedCustomObject( + //await this.customObjectsApi.patchNamespacedCustomObject( + // patch : https://stackoverflow.com/questions/67520468/patch-k8s-custom-resource-with-kubernetes-client-node + // https://github.com/kubernetes-client/javascript/blob/master/examples/patch-example.js + "application.kubero.dev", + "v1alpha1", + namespace, + "kuberoapps", + app.name, + appl + ).catch(error => { + this.logger.debug(error); + }) + } + + public async deleteApp(pipelineName: string, phaseName: string, appName: string, context: string) { + this.logger.debug("delete app: " + appName); + + let namespace = pipelineName+'-'+phaseName; + this.kc.setCurrentContext(context); + + await this.customObjectsApi.deleteNamespacedCustomObject( + "application.kubero.dev", + "v1alpha1", + namespace, + "kuberoapps", + appName + ).catch(error => { + this.logger.debug(error); + }) + } + + public async getApp(pipelineName: string, phaseName: string, appName: string, context: string) { + + let namespace = pipelineName+'-'+phaseName; + this.kc.setCurrentContext(context); + + let app = await this.customObjectsApi.getNamespacedCustomObject( + "application.kubero.dev", + "v1alpha1", + namespace, + "kuberoapps", + appName + ).catch(error => { + this.logger.debug(error); + }) + + return app; + } + + public async getAppsList(namespace: string, context: string): Promise { + this.kc.setCurrentContext(context); + try { + let appslist = await this.customObjectsApi.listNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoapps' + ) + return appslist.body as IKubectlAppList; + } catch (error) { + //this.logger.debug(error); + this.logger.debug("getAppsList: error getting apps"); + } + const appslist = {} as IKubectlAppList; + appslist.items = []; + return appslist as IKubectlAppList; + } + + public async restartApp(pipelineName: string, phaseName: string, appName: string, workloadType: string, context: string) { + this.logger.debug("restart app: " + appName); + this.kc.setCurrentContext(context); + + let namespace = pipelineName+'-'+phaseName; + let deploymentName = appName+'-kuberoapp-'+workloadType; + const date = new Date(); + + // format : https://jsonpatch.com/ + const patch = [ + { + op: 'add', + path: '/spec/restartedAt', + value: { + 'restartedAt': date.toISOString() + } + }, + ]; + + const apiVersion = "v1alpha1" + const group = "application.kubero.dev" + const plural = "kuberoapps" + + const options = { "headers": { "Content-type": 'application/json-patch+json' } }; + this.customObjectsApi.patchNamespacedCustomObject( + group, + apiVersion, + namespace, + plural, + appName, + patch, + undefined, + undefined, + undefined, + options + ).then(() => { + this.logger.debug(`Deployment ${deploymentName} in Pipeline ${namespace} updated`); + }).catch(error => { + if (error.body.message) { + this.logger.debug('ERROR: '+error.body.message); + } + this.logger.debug('ERROR: '+error); + }); + }; + + public async getOperators() { + // TODO list operators from all clusters + let operators = { items: [] }; + try { + let response = await this.customObjectsApi.listNamespacedCustomObject( + 'operators.coreos.com', + 'v1alpha1', + 'operators', + 'clusterserviceversions' + ) + //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) { + //this.logger.debug(error); + this.logger.debug("error getting operators"); + } + + return operators.items; + } + + public async getCustomresources() { + // TODO list operators from all clusters + let operators = { items: [] }; + try { + let response = await this.customObjectsApi.listClusterCustomObject( + 'apiextensions.k8s.io', + 'v1', + 'customresourcedefinitions' + ) + operators = response.body as any // TODO : fix type. This is a hacky way to get the type to work + } catch (error: any) { + //this.logger.debug(error); + this.logger.debug("error getting customresources"); + } + + return operators.items; + } + + public async getPods(namespace: string, context: string): Promise{ + const pods = await this.coreV1Api.listNamespacedPod(namespace); + return pods.body.items; + } + + public async createEvent(type: "Normal" | "Warning",reason: string, eventName: string, message: string) { + this.logger.debug("create event: " + eventName); + + const date = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days in the future //TODO make this configurable + const event = new CoreV1Event(); + event.apiVersion = "v1"; + event.kind = "Event"; + event.type = type; + event.message = message; + event.reason = reason; + event.metadata = { + name: eventName+'.'+Date.now().toString(), + namespace: process.env.KUBERO_NAMESPACE || 'kubero', + }; + event.involvedObject = { + kind: "Kubero", + namespace: process.env.KUBERO_NAMESPACE || 'kubero', + }; + + await this.coreV1Api.createNamespacedEvent( + process.env.KUBERO_NAMESPACE || 'kubero', + event + ).catch(error => { + this.logger.debug(error); + } + )}; + + public async getEvents(namespace: string): Promise { + try { + const events = await this.coreV1Api.listNamespacedEvent(namespace); + return events.body.items; + } catch (error) { + //this.logger.debug(error); + this.logger.debug("getEvents: error getting events"); + } + const events = {} as CoreV1EventList; + events.items = []; + return events.items; + } + + public async getPodMetrics(namespace: string, appName: string): Promise { //TODO make this a real type + const ret: { + name: string; + namespace: string; + memory: { + unit: string; + request: number; + limit: number; + usage: number; + percentage: number; + }; + cpu: { + unit: string; + request: number; + limit: number; + usage: number; + percentage: number; + }; + }[] = []; + + try { + const metrics = await this.metricsApi.getPodMetrics(namespace); + + for (let i = 0; i < metrics.items.length; i++) { + const metric = metrics.items[i]; + + if ( !metric.metadata.name.startsWith(appName+"-") ) continue; + + const pod = await this.coreV1Api.readNamespacedPod(metric.metadata.name, namespace); + const requestCPU = this.normalizeCPU(pod.body.spec?.containers[0].resources?.requests?.cpu || '0'); + const requestMemory = this.normalizeMemory(pod.body.spec?.containers[0].resources?.requests?.memory || '0'); + const limitsCPU = this.normalizeCPU(pod.body.spec?.containers[0].resources?.limits?.cpu || '0'); + const limitsMemory = this.normalizeMemory(pod.body.spec?.containers[0].resources?.limits?.memory || '0'); + const usageCPU = this.normalizeCPU(metric.containers[0].usage.cpu); + const usageMemory = this.normalizeMemory(metric.containers[0].usage.memory); + const percentageCPU = Math.round(usageCPU / limitsCPU * 100); + const percentageMemory = Math.round(usageMemory / limitsMemory * 100); + + /* debug caclulation *//* + console.log("resource CPU : " + requestCPU, pod.body.spec?.containers[0].resources?.requests?.cpu) + console.log("limits CPU : " + limitsCPU, pod.body.spec?.containers[0].resources?.limits?.cpu) + console.log("usage CPU : " + usageCPU, metric.containers[0].usage.cpu) + console.log("percent CPU : " + percentageCPU + "%") + console.log("resource Memory : " + requestMemory, pod.body.spec?.containers[0].resources?.limits?.cpu) + console.log("limits Memory : " + limitsMemory, pod.body.spec?.containers[0].resources?.limits?.memory) + console.log("usage Memory : " + usageMemory, metric.containers[0].usage.memory) + console.log("percent Memory : " + percentageMemory + "%") + console.log("------------------------------------") + /* end debug calculations*/ + + const m = { + name: metric.metadata.name, + namespace: metric.metadata.namespace, + memory : { + unit: 'Mi', + request: requestMemory, + limit: limitsMemory, + usage: usageMemory, + percentage: percentageMemory + }, + cpu : { + unit: 'm', + request: requestCPU, + limit: limitsCPU, + usage: usageCPU, + percentage: percentageCPU + } + } + ret.push(m); + } + } catch (error: any) { + this.logger.debug('ERROR fetching metrics: '+ error); + } + + return ret; + } + + private normalizeCPU(resource: string): number { + + const regex = /([0-9]+)([a-zA-Z]*)/; + const matches = resource.match(regex); + + let value = 0; + let unit = ''; + if (matches !== null && matches[1]) { + value = parseInt(matches[1]) + } + if (matches !== null && matches[2]) { + unit = matches[2] + } + + //console.log("CPU unit: " + unit + " value: " + value + " :: " +resource); + switch (unit) { + case 'm': + return value / 1; + case 'n': + return Math.round(value / 1000000); + default: + return value * 1000; + } + return 0; + } + + + private normalizeMemory(resource: string): number { + + const regex = /([0-9]+)([a-zA-Z]*)/; + const matches = resource.match(regex); + + let value = 0; + let unit = ''; + if (matches !== null && matches[1]) { + value = parseInt(matches[1]) + } + if (matches !== null && matches[2]) { + unit = matches[2] + } + //console.log("CPU unit: " + unit + " value: " + value + " :: " +resource); + + switch (unit) { + case 'Gi': + return value * 1000; + case 'Mi': + return value / 1; + case 'Ki': + return Math.round(value / 1000); + default: + return value; + } + return 0; + } + + public async getNodeMetrics(): Promise { + const metrics = await this.metricsApi.getNodeMetrics(); + return metrics.items; + } + + private getPodUptimeMS(pod: V1Pod): number { + const startTime = pod.status?.startTime; + if (startTime) { + const start = new Date(startTime); + const now = new Date(); + const uptime = now.getTime() - start.getTime(); + return uptime; + } + return -1; + } + + public async getPodUptimes(namespace: string): Promise { + const pods = await this.coreV1Api.listNamespacedPod(namespace); + const ret = Object(); + for (let i = 0; i < pods.body.items.length; i++) { + const pod = pods.body.items[i]; + const uptime = this.getPodUptimeMS(pod); + if (pod.metadata && pod.metadata.name) { + ret[pod.metadata.name] = { + ms: uptime, + formatted: this.formatUptime(uptime) + } + } + } + return ret; + } + + private formatUptime(uptime: number): string { + // 0-120s show seconds + // 2-10m show minutes and seconds + // 10-120m show minutes + // 2-48h show hours and minutes + // 2-30d show days and hours + // >30d show date + + if (uptime < 0) { + return ''; + } + if (uptime < 120000) { + const seconds = Math.floor(uptime / 1000); + return seconds + "s"; + } + if (uptime < 600000) { + const minutes = Math.floor(uptime / (1000 * 60)); + const seconds = Math.floor((uptime - (minutes * 1000 * 60)) / 1000); + if (seconds > 0) { + return minutes + "m" + seconds + "s"; + } + return minutes + "m"; + } + if (uptime < 7200000) { + const minutes = Math.floor(uptime / (1000 * 60)); + return minutes + "m"; + } + if (uptime < 172800000) { + const hours = Math.floor(uptime / (1000 * 60 * 60)); + const minutes = Math.floor((uptime - (hours * 1000 * 60 * 60)) / (1000 * 60)); + if (minutes > 0) { + return hours + "h" + minutes + "m"; + } + return hours + "h"; + } + //if (uptime < 2592000000) { + const days = Math.floor(uptime / (1000 * 60 * 60 * 24)); + const hours = Math.floor((uptime - (days * 1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + if (hours > 0) { + return days + "d" + hours + "h"; + } + return days + "d"; + //} + + } + + public async getStorageglasses(): Promise { + let ret: { name: string | undefined; provisioner: string; reclaimPolicy: string | undefined; volumeBindingMode: string | undefined; }[] = []; + try { + const storageClasses = await this.storageV1Api.listStorageClass(); + for (let i = 0; i < storageClasses.body.items.length; i++) { + const sc = storageClasses.body.items[i]; + const storageClass = { + name: sc.metadata?.name, + provisioner: sc.provisioner, + reclaimPolicy: sc.reclaimPolicy, + volumeBindingMode: sc.volumeBindingMode, + //allowVolumeExpansion: sc.allowVolumeExpansion, + //parameters: sc.parameters + } + ret.push(storageClass); + } + } catch (error) { + console.log(error); + console.log('ERROR fetching storageclasses'); + } + return ret; + } + + public async getIngressClasses(): Promise { + // undefind = default + let ret = [{ + name: undefined + }] as Object[]; + try { + const ingressClasses = await this.networkingV1Api.listIngressClass(); + for (let i = 0; i < ingressClasses.body.items.length; i++) { + const ic = ingressClasses.body.items[i]; + const ingressClass = { + name: ic.metadata?.name, + } + ret.push(ingressClass); + } + } catch (error) { + console.log(error); + console.log('ERROR fetching ingressclasses'); + } + return ret; + } + + private async deleteScanJob(namespace: string, name: string): Promise { + try { + await this.batchV1Api.deleteNamespacedJob(name, namespace); + // wait for job to be deleted + await new Promise(resolve => setTimeout(resolve, 1000)); + } catch (error) { + //console.log(error); + console.log('ERROR deleting job: '+name+' ' +namespace); + } + } + + public async createScanRepoJob(namespace: string, app: string, gitrepo: string, branch: string): Promise { + await this.deleteScanJob(namespace, app+'-kuberoapp-vuln'); + const job = { + apiVersion: 'batch/v1', + kind: 'Job', + metadata: { + name: app+'-kuberoapp-vuln', + namespace: namespace, + }, + spec: { + ttlSecondsAfterFinished: 86400, + completions: 1, + template: { + metadata: { + labels: { + vulnerabilityscan: app + } + }, + spec: { + restartPolicy: 'Never', + securityContext: { + runAsUser: 1000 + }, + containers: [ + { + name: 'trivy-repo-scan', + image: "aquasec/trivy:latest", + command: [ + "trivy", + "repo", + gitrepo, + "--branch", + branch, + "-q", + "-f", + "json", + "--scanners", + "vuln,secret,config", + "--cache-dir", + "/tmp/trivy", + "--exit-code", + "0" + ], + } + ] + } + } + } + }; + try { + return await this.batchV1Api.createNamespacedJob(namespace, job); + } catch (error) { + console.log(error); + console.log('ERROR creating Repo scan job: '+app+' ' +namespace); + } + } + + public async createScanImageJob(namespace: string, app: string, image: string, tag: string, withCredentials: boolean): Promise { + await this.deleteScanJob(namespace, app+'-kuberoapp-vuln'); + let job = { + apiVersion: 'batch/v1', + kind: 'Job', + metadata: { + name: app+'-kuberoapp-vuln', + namespace: namespace, + }, + spec: { + ttlSecondsAfterFinished: 86400, + completions: 1, + backoffLimit: 1, + template: { + metadata: { + labels: { + vulnerabilityscan: app + } + }, + spec: { + restartPolicy: 'Never', + securityContext: { + runAsUser: 1000 + }, + containers: [ + { + name: 'trivy-repo-scan', + image: "aquasec/trivy:latest", + command: [ + "trivy", + "image", + image+":"+tag, + "-q", + "-f", + "json", + "--scanners", + "vuln", + "--cache-dir", + "/tmp/trivy", + "--exit-code", + "0" + ], + env: [] as { name: string; valueFrom: { secretKeyRef: { name: string; key: string; optional: true; }; }; }[], + } + ] + } + } + } + }; + + if (withCredentials) { + job.spec.template.spec.containers[0].env = [ + { + name: 'TRIVY_USERNAME', + valueFrom: { + secretKeyRef: { + name: 'registry-credentials', + key: 'username', + optional: true + } + } + }, + { + name: 'TRIVY_PASSWORD', + valueFrom: { + secretKeyRef: { + name: 'registry-credentials', + key: 'password', + optional: true + } + } + } + ] + } + + try { + return await this.batchV1Api.createNamespacedJob(namespace, job); + } catch (error) { + console.log(error); + console.log('ERROR creating Image scan job'); + } + } + + public async getVulnerabilityScanLogs(namespace: string, logPod: string): Promise { + + try { + const logs = await this.coreV1Api.readNamespacedPodLog(logPod, namespace, undefined, false); + return logs.body; + } catch (error) { + console.log(error); + console.log('ERROR fetching scan logs'); + } + } + + public async getLatestPodByLabel(namespace: string, label: string ): Promise { + + try { + const pods = await this.coreV1Api.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, label); + let latestPod: V1Pod | null = null; + for (let i = 0; i < pods.body.items.length; i++) { + const pod = pods.body.items[i]; + if (latestPod === null) { + latestPod = pod; + } else { + if ( + pod.metadata?.creationTimestamp && latestPod.metadata?.creationTimestamp && + pod.metadata?.creationTimestamp > latestPod.metadata?.creationTimestamp) { + latestPod = pod; + } + } + } + + return { + name: latestPod?.metadata?.name, + status: latestPod?.status?.phase, + startTime: latestPod?.status?.startTime, + containerStatuses: latestPod?.status?.containerStatuses + + }; + + //return latestPod?.metadata?.name + } catch (error) { + console.log(error); + console.log('ERROR fetching pod by label'); + } + } + + public async deployApp(namespace: string, appName: string, tag: string) { + + let deploymentName = appName+'-kuberoapp-web'; + console.log("deploy app: " + appName, ",namespace: " + namespace, ",tag: " + tag, ",deploymentName: " + deploymentName); + + // format : https://jsonpatch.com/ + const patch = [ + { + op: 'replace', + path: '/spec/image/tag', + value: tag, + }, + ]; + + const apiVersion = "v1alpha1" + const group = "application.kubero.dev" + const plural = "kuberoapps" + + const options = { "headers": { "Content-type": 'application/json-patch+json' } }; + this.customObjectsApi.patchNamespacedCustomObject( + group, + apiVersion, + namespace, + plural, + appName, + patch, + undefined, + undefined, + undefined, + options + ).then(() => { + this.logger.debug(`Deployment ${deploymentName} in Pipeline ${namespace} updated`); + }).catch(error => { + if (error.body.message) { + this.logger.debug('ERROR: '+error.body.message); + } + this.logger.debug('ERROR: '+error); + }); + }; + + public async getAllIngress(): Promise { + const ingresses = await this.networkingV1Api.listIngressForAllNamespaces(); + return ingresses.body.items; + } + + public async execInContainer(namespace: string, podName: string, containerName: string, command: string, stdin: internal.PassThrough): Promise { + //const command = ['ls', '-al', '.'] + //const command = ['bash'] + //const command = "bash" + const ws = await this.exec.exec( + namespace, + podName, + containerName, + command, + process.stdout as stream.Writable, + process.stderr as stream.Writable, + stdin, + true + ); + return ws + } + + public async getKuberoConfig(namespace: string): Promise { + try { + const config = await this.customObjectsApi.getNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoes', + 'kubero' + ) + //console.log(config.body); + return config.body; + } catch (error) { + //this.logger.debug(error); + this.logger.debug("getKuberoConfig: error getting config"); + } + } + + + public async updateKuberoConfig(namespace: string, config: any) { + const patch = [ + { + op: 'replace', + path: '/spec', + value: config.spec, + }, + ]; + + const options = { "headers": { "Content-type": 'application/json-patch+json' } }; + try { + await this.customObjectsApi.patchNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoes', + 'kubero', + patch, + undefined, + undefined, + undefined, + options + ) + } catch (error) { + this.logger.debug(error); + } + } + + public async updateKuberoSecret(namespace: string, secret: any) { + + const patch = [ + { + op: 'replace', + path: '/stringData', + value: secret, + }, + ]; + + const options = { "headers": { "Content-type": 'application/json-patch+json' } }; + try { + await this.coreV1Api.patchNamespacedSecret( + 'kubero-secrets', + namespace, + patch, + undefined, + undefined, + undefined, + undefined, + undefined, + options + ) + } catch (error) { + this.logger.debug(error); + } + } + + public async createBuildJob( + namespace: string, + appName: string, + pipelineName: string, + buildstrategy: 'buildpacks' | 'dockerfile' | 'nixpacks' | 'plain', + dockerfilePath: string | undefined, + git: { + url: string, + ref: string + }, + repository: { + image: string, + tag: string + } + ): Promise { + this.logger.error('refactoring: loadJob not implemented'); + //let job = loadJob(buildstrategy) as any + let job = new Object() as any; + + const id = new Date().toISOString().replace(/[-:]/g, '').replace(/[T]/g, '-').substring(0, 13); + const name = appName + "-" + pipelineName + "-" + id; + + job.metadata.name = name.substring(0, 53); // max 53 characters allowed within kubernetes + //job.metadata.namespace = namespace; + job.metadata.labels['job-name'] = name.substring(0, 53); + job.metadata.labels['batch.kubernetes.io/job-name'] = name.substring(0, 53); + job.metadata.labels['kuberoapp'] = appName; + job.metadata.labels['kuberopipeline'] = pipelineName; + job.spec.template.metadata.labels['job-name'] = name.substring(0, 53); + job.spec.template.metadata.labels['batch.kubernetes.io/job-name'] = name.substring(0, 53); + job.spec.template.metadata.labels['kuberoapp'] = appName; + job.spec.template.metadata.labels['kuberopipeline'] = pipelineName; + job.spec.template.spec.serviceAccountName = appName+'-kuberoapp'; + job.spec.template.spec.serviceAccount = appName+'-kuberoapp'; + job.spec.template.spec.initContainers[0].env[0].value = git.url; + job.spec.template.spec.initContainers[0].env[1].value = git.ref; + job.spec.template.spec.containers[0].env[0].value = repository.image + job.spec.template.spec.containers[0].env[1].value = repository.tag+"-"+id; + job.spec.template.spec.containers[0].env[2].value = appName; + + if (buildstrategy === 'buildpacks') { + // configure build container + job.spec.template.spec.initContainers[2].args[1] = repository.image+":"+repository.tag+"-"+id; + } + if (buildstrategy === 'dockerfile') { + // configure push container + job.spec.template.spec.initContainers[1].env[1].value = repository.image+":"+repository.tag+"-"+id; + job.spec.template.spec.initContainers[1].env[2].value = dockerfilePath; + } + if (buildstrategy === 'nixpacks') { + // configure push container + job.spec.template.spec.initContainers[2].env[1].value = repository.image+":"+repository.tag+"-"+id; + job.spec.template.spec.initContainers[2].env[2].value = dockerfilePath; + } + + console.log("create build job: " + job); + + try { + return await this.batchV1Api.createNamespacedJob(namespace, job); + } catch (error) { + console.log(error); + console.log('ERROR creating build job'); + } + } + + public async deleteKuberoBuildJob(namespace: string, buildName: string) { + try { + await this.batchV1Api.deleteNamespacedJob(buildName, namespace) + } catch (error) { + this.logger.debug(error); + } + } + + + public async getJob(namespace: string, jobName: string): Promise { + try { + const job = await this.batchV1Api.readNamespacedJob(jobName, namespace) + return job.body; + } catch (error) { + this.logger.debug(error); + this.logger.debug("getJob: error getting job"); + } + } + + public async getJobs(namespace: string): Promise { + try { + const jobs = await this.batchV1Api.listNamespacedJob(namespace) + return jobs.body; + } catch (error) { + this.logger.debug(error); + this.logger.debug("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-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts b/server-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts new file mode 100644 index 00000000..0b5583cd --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts @@ -0,0 +1,98 @@ +import { Pipeline } from './pipeline'; +import { IPipeline } from '../pipelines.interface'; + +describe('Pipeline', () => { + const mockPipeline: IPipeline = { + name: 'test-pipeline', + domain: 'example.com', + dockerimage: 'test-image', + reviewapps: true, + phases: [], + buildpack: { + name: 'test-buildpack', + language: 'nodejs', + fetch: { + repository: 'https://github.com/test/repo', + tag: 'latest', + readOnlyAppStorage: true, + securityContext: { + runAsUser: 1000, + runAsGroup: 3000, + runAsNonRoot: false, + readOnlyRootFilesystem: false, + allowPrivilegeEscalation: false, + capabilities: { + add: [], + drop: [] + } + } + }, + build: { + repository: 'https://github.com/test/repo', + tag: 'latest', + readOnlyAppStorage: true, + securityContext: { + runAsUser: 1000, + runAsGroup: 3000, + runAsNonRoot: false, + readOnlyRootFilesystem: false, + allowPrivilegeEscalation: false, + capabilities: { + add: [], + drop: [] + } + } + }, + run: { + repository: 'https://github.com/test/repo', + tag: 'latest', + readOnlyAppStorage: true, + securityContext: { + runAsUser: 1000, + runAsGroup: 3000, + runAsNonRoot: false, + readOnlyRootFilesystem: false, + allowPrivilegeEscalation: false, + capabilities: { + add: [], + drop: [] + } + } + }, + tag: 'latest' + }, + deploymentstrategy: 'git', + buildstrategy: 'plain', + git: { + keys: {}, + repository: { + admin: true, + }, + webhook: {} + + }, + registry: { + host: 'test-host', + username: 'test-user', + password: 'test-password' + } + }; + + it('should be defined', () => { + expect(new Pipeline(mockPipeline)).toBeDefined(); + }); + + it('should have correct properties', () => { + const pipeline = new Pipeline(mockPipeline); + expect(pipeline.name).toBe(mockPipeline.name); + expect(pipeline.domain).toBe(mockPipeline.domain); + expect(pipeline.dockerimage).toBe(mockPipeline.dockerimage); + expect(pipeline.reviewapps).toBe(mockPipeline.reviewapps); + expect(pipeline.phases).toBe(mockPipeline.phases); + expect(pipeline.buildpack).toBe(mockPipeline.buildpack); + expect(pipeline.deploymentstrategy).toBe(mockPipeline.deploymentstrategy); + expect(pipeline.buildstrategy).toBe(mockPipeline.buildstrategy); + expect(pipeline.git).toBe(mockPipeline.git); + expect(pipeline.registry).toBe(mockPipeline.registry); + }); +}); diff --git a/server-refactored-v3/src/pipelines/pipeline/pipeline.ts b/server-refactored-v3/src/pipelines/pipeline/pipeline.ts new file mode 100644 index 00000000..8c4ace18 --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipeline/pipeline.ts @@ -0,0 +1,58 @@ +import { + IPipeline, + IPipelinePhase, + IgitLink, + IKubectlPipeline, + IRegistry +} from '../pipelines.interface'; + +import { IBuildpack } from '../../settings/settings.interface'; +import { IKubectlMetadata } from '../../kubectl/kubectl.interface'; + + +export class Pipeline implements IPipeline { + public name: string; + public domain: string; + public dockerimage: string; + public reviewapps: boolean; + public phases: IPipelinePhase[]; + public buildpack: IBuildpack; + public deploymentstrategy: 'git' | 'docker'; + public buildstrategy : 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; + public git: IgitLink; + public registry: IRegistry; + + constructor( + pl: IPipeline, + ) { + this.name = pl.name; + this.domain = pl.domain; + this.reviewapps = pl.reviewapps; + this.phases = pl.phases; + this.buildpack = pl.buildpack; + this.dockerimage = pl.dockerimage; + this.deploymentstrategy = pl.deploymentstrategy; + this.buildstrategy = pl.buildstrategy; + this.git = pl.git; + this.registry = pl.registry; + } +} + +export class KubectlPipeline implements IKubectlPipeline { + public apiVersion: string; + public kind: string; + public metadata: IKubectlMetadata; + public spec: Pipeline; + + constructor(pipeline: IPipeline) { + this.apiVersion = "application.kubero.dev/v1alpha1"; + this.kind = "KuberoPipeline"; + this.metadata = { + name: pipeline.name, + labels: { + manager: 'kubero', + }, + }; + this.spec = pipeline; + } +} diff --git a/server-refactored-v3/src/pipelines/pipelines.interface.ts b/server-refactored-v3/src/pipelines/pipelines.interface.ts new file mode 100644 index 00000000..9784c3d1 --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipelines.interface.ts @@ -0,0 +1,54 @@ +import { IGithubRepository } from "src/apps/apps.interface"; +import { IBuildpack } from "src/settings/settings.interface"; +import { IKubectlMetadata } from "../kubectl/kubectl.interface"; + +export interface IPipeline { + name: string; + domain: string; + reviewapps: boolean; + phases: IPipelinePhase[]; + buildpack: IBuildpack + git: IgitLink; + registry: IRegistry; + dockerimage: string; + deploymentstrategy: 'git' | 'docker', + buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks', + resourceVersion?: string; // required to update resource, not part of spec +} +export interface IgitLink { + keys: { + priv?: string, + pub?: string, + }, + provider?: string, + repository?: IGithubRepository + webhook: object; +} + +export interface IPipelinePhase { + name: string; + enabled: boolean; + context: string; + defaultEnvvars: {}[]; + domain: string; + //apps: IApp[]; +} + +export interface IRegistry { + host: string; + username: string; + password: string; +} + +export interface IKubectlPipeline { + apiVersion: string; + kind: string; + metadata: IKubectlMetadata, + spec: IPipeline +} +export interface IKubectlPipelineList { + apiVersion: string; + kind: string; + metadata: IKubectlMetadata, + items: IKubectlPipeline[] +} \ No newline at end of file diff --git a/server-refactored-v3/src/templates/template.spec.ts b/server-refactored-v3/src/templates/template.spec.ts new file mode 100644 index 00000000..dee9efed --- /dev/null +++ b/server-refactored-v3/src/templates/template.spec.ts @@ -0,0 +1,7 @@ +import { Template } from './template'; + +describe('Template', () => { + it('should be defined', () => { + expect(new Template()).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/templates/template.ts b/server-refactored-v3/src/templates/template.ts new file mode 100644 index 00000000..39ae700a --- /dev/null +++ b/server-refactored-v3/src/templates/template.ts @@ -0,0 +1,111 @@ +import { ITemplate, IKubectlTemplate } from './templates.interface'; +import { IApp, IExtraVolume, ICronjob } from '../apps/apps.interface'; +import { IAddon } from '../addons/addons.interface'; +import { IKubectlMetadata } from '../kubectl/kubectl.interface'; + +export class Template implements ITemplate{ + public name: string + public deploymentstrategy: 'git' | 'docker' + public envVars: {}[] = [] + /* + public serviceAccount: { + annotations: Object + create: boolean, + name: string, + }; + */ + public extraVolumes: IExtraVolume[] = [] + public cronjobs: ICronjob[] = [] + public addons: IAddon[] = [] + + public web: { + replicaCount: number + } + + public worker: { + replicaCount: number + } + + public image: { + containerPort: number, + pullPolicy?: 'Always', + repository: string, + tag: string, + /* + run: { + repository: string, + tag: string, + readOnlyAppStorage?: boolean, + securityContext: ISecurityContext + } + */ + }; + constructor( + app: IApp + ) { + this.name = app.name + this.deploymentstrategy = app.deploymentstrategy + + this.envVars = app.envVars + + //this.serviceAccount = app.serviceAccount; + + this.extraVolumes = app.extraVolumes + + this.cronjobs = app.cronjobs + + this.addons = app.addons + + this.web = { + replicaCount: app.web.replicaCount + } + this.worker = { + replicaCount: app.worker.replicaCount + } + + this.image = { + containerPort: app.image.containerPort, + pullPolicy: 'Always', + repository: app.image.repository || 'ghcr.io/kubero-dev/idler', + tag: app.image.tag || 'v1', + //run: app.image.run, + } + + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + //this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) + } +} + +export class KubectlTemplate implements IKubectlTemplate{ + apiVersion: string; + kind: string; + metadata: IKubectlMetadata; + spec: Template; + + constructor(app: IApp) { + this.apiVersion = "application.kubero.dev/v1alpha1"; + this.kind = "KuberoApp"; + this.metadata = { + name: app.name, + annotations: { + 'kubero.dev/template.architecture': '[]', + 'kubero.dev/template.description': '', + 'kubero.dev/template.icon': '', + 'kubero.dev/template.installation': '', + 'kubero.dev/template.links': '[]', + 'kubero.dev/template.screenshots': '[]', + 'kubero.dev/template.source': '', + 'kubero.dev/template.categories': '[]', + 'kubero.dev/template.title': '', + 'kubero.dev/template.website': '' + }, + labels: { + manager: 'kubero', + } + } + this.spec = new Template(app); + } +} + + \ No newline at end of file diff --git a/server-refactored-v3/src/templates/templates.interface.ts b/server-refactored-v3/src/templates/templates.interface.ts new file mode 100644 index 00000000..75ee57c3 --- /dev/null +++ b/server-refactored-v3/src/templates/templates.interface.ts @@ -0,0 +1,48 @@ +import { IExtraVolume, ICronjob } from '../apps/apps.interface'; +import { IKubectlMetadata } from '../kubectl/kubectl.interface'; +import { ISecurityContext } from "../settings/settings.interface" +import { IAddon } from '../addons/addons.interface'; + +export interface ITemplate { + name: string, + deploymentstrategy: 'git' | 'docker', + envVars: {}[], + serviceAccount?: { + annotations: {}, + create: boolean, + name: string, + }, + image : { + repository: string, + tag: string, + pullPolicy?: 'Always', + containerPort: number, + run?: { + repository: string, + readOnlyAppStorage?: boolean, + tag: string, + readOnly?: boolean, + securityContext: ISecurityContext + } + } + + web: { + replicaCount: number + } + + worker: { + replicaCount: number + } + + extraVolumes: IExtraVolume[], + cronjobs: ICronjob[] + addons: IAddon[] +} + +export interface IKubectlTemplate +{ + apiVersion: string; + kind: string; + metadata: IKubectlMetadata + spec: ITemplate; +} \ No newline at end of file diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 035dba5a..84e1ad93 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -592,13 +592,6 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@isaacs/fs-minipass@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" - integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== - dependencies: - minipass "^7.0.4" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -855,45 +848,48 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jsep-plugin/assignment@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@jsep-plugin/assignment/-/assignment-1.3.0.tgz#fcfc5417a04933f7ceee786e8ab498aa3ce2b242" - integrity sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ== - -"@jsep-plugin/regex@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@jsep-plugin/regex/-/regex-1.0.4.tgz#cb2fc423220fa71c609323b9ba7f7d344a755fcc" - integrity sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg== - -"@kubernetes/client-node@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-1.0.0.tgz#17ee4c7426d47c5da861d4653b24964e476dfb7e" - integrity sha512-a8NSvFDSHKFZ0sR1hbPSf8IDFNJwctEU5RodSCNiq/moRXWmrdmqhb1RRQzF+l+TSBaDgHw3YsYNxxE92STBzw== +"@kubernetes/client-node@^0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.20.0.tgz#4447ae27fd6eef3d4830a5a039f3b84ffd5c5913" + integrity sha512-xxlv5GLX4FVR/dDKEsmi4SPeuB49aRc35stndyxcC73XnUEEwF39vXbROpHOirmDse8WE9vxOjABnSVS+jb7EA== dependencies: "@types/js-yaml" "^4.0.1" - "@types/node" "^22.0.0" - "@types/node-fetch" "^2.6.9" - "@types/stream-buffers" "^3.0.3" - "@types/tar" "^6.1.1" - "@types/ws" "^8.5.4" - form-data "^4.0.0" + "@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 "^10.2.0" - node-fetch "^2.6.9" - openid-client "^6.1.3" + jsonpath-plus "^7.2.0" + request "^2.88.0" rfc4648 "^1.3.0" stream-buffers "^3.0.2" - tar "^7.0.0" - tmp-promise "^3.0.2" - tslib "^2.5.0" - ws "^8.18.0" + tar "^6.1.11" + tslib "^2.4.1" + ws "^8.11.0" + optionalDependencies: + openid-client "^5.3.0" "@lukeed/csprng@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@microsoft/tsdoc@0.15.0": version "0.15.0" resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz#f29a55df17cb6e87cfbabce33ff6a14a9f85076d" @@ -1026,15 +1022,6 @@ webpack "5.97.1" webpack-node-externals "3.0.0" -"@nestjs/common@^10.0.2": - version "10.4.15" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.15.tgz#27c291466d9100eb86fdbe6f7bbb4d1a6ad55f70" - integrity sha512-vaLg1ZgwhG29BuLDxPA9OAcIlgqzp9/N8iG0wGapyUNTf4IY4O6zAHgN6QalwLhFxq7nOI021vdRojR1oF3bqg== - dependencies: - uid "2.0.2" - iterare "1.2.1" - tslib "2.8.1" - "@nestjs/common@^11.0.1": version "11.0.6" resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.0.6.tgz#ddd534e437e6ef581b06d38cfc9e97a6ed40b14b" @@ -1159,20 +1146,6 @@ dependencies: consola "^3.2.3" -"@otwld/nestjs-kubernetes@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@otwld/nestjs-kubernetes/-/nestjs-kubernetes-1.0.3.tgz#dca3630dc23c2ab1da2f550fc612902ee456da14" - integrity sha512-tVhGZwsptYOgq7J96B77jF+eYlNQ/y7pnl1yAwyvgp9a7cU6zKvQ3PgJnoTEPajP+WY5gQwGyLK301RAN6wqOQ== - dependencies: - "@kubernetes/client-node" "^1.0.0" - "@nestjs/common" "^10.0.2" - tslib "^2.3.0" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - "@pkgr/core@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" @@ -1378,6 +1351,13 @@ dependencies: "@babel/types" "^7.20.7" +"@types/bcrypt@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.2.tgz#22fddc11945ea4fbc3655b3e8b8847cc9f811477" + integrity sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ== + dependencies: + "@types/node" "*" + "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -1386,6 +1366,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/caseless@*": + version "0.12.5" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5" + integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg== + "@types/connect@*": version "3.4.38" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" @@ -1510,14 +1495,6 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== -"@types/node-fetch@^2.6.9": - version "2.6.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" - integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== - dependencies: - "@types/node" "*" - form-data "^4.0.0" - "@types/node@*", "@types/node@>=10.0.0", "@types/node@^22.10.7": version "22.12.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.12.0.tgz#bf8af3b2af0837b5a62a368756ff2b705ae0048c" @@ -1525,12 +1502,12 @@ dependencies: undici-types "~6.20.0" -"@types/node@^22.0.0": - version "22.13.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.0.tgz#d376dd9a0ee2f9382d86c2d5d7beb4d198b4ea8c" - integrity sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA== +"@types/node@^20.1.1": + version "20.17.16" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.16.tgz#b33b0edc1bf925b27349e494b871ca4451fabab4" + integrity sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw== dependencies: - undici-types "~6.20.0" + undici-types "~6.19.2" "@types/oauth@*": version "0.9.6" @@ -1591,6 +1568,16 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== +"@types/request@^2.47.1": + version "2.48.12" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" + integrity sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + "@types/send@*": version "0.17.4" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" @@ -1613,13 +1600,6 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== -"@types/stream-buffers@^3.0.3": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/stream-buffers/-/stream-buffers-3.0.7.tgz#0b719fa1bd2ca2cc0908205a440e5e569e1aa21e" - integrity sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw== - dependencies: - "@types/node" "*" - "@types/superagent@^8.1.0": version "8.1.9" resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" @@ -1638,15 +1618,12 @@ "@types/methods" "^1.1.4" "@types/superagent" "^8.1.0" -"@types/tar@^6.1.1": - version "6.1.13" - resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.13.tgz#9b5801c02175344101b4b91086ab2bbc8e93a9b6" - integrity sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw== - dependencies: - "@types/node" "*" - minipass "^4.0.0" +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== -"@types/ws@^8.5.4": +"@types/ws@^8.5.3": version "8.5.14" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.14.tgz#93d44b268c9127d96026cf44353725dd9b6c3c21" integrity sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw== @@ -1975,6 +1952,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + accepts@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" @@ -2008,6 +1990,13 @@ acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv-formats@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" @@ -2044,7 +2033,7 @@ ajv@8.17.1, ajv@^8.0.0, ajv@^8.9.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2111,11 +2100,24 @@ append-field@^1.0.0: resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + arch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/arch/-/arch-3.0.0.tgz#a44e7077da4615fc5f1e3da21fbfc201d2c1817c" integrity sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q== +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -2148,6 +2150,18 @@ asap@^2.0.0: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + async@^3.2.3: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" @@ -2158,6 +2172,16 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" + integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== + b4a@^1.6.4: version "1.6.7" resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" @@ -2251,6 +2275,21 @@ base64url@3.x.x: resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +bcrypt@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" + bin-version-check@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-5.1.0.tgz#788e80e036a87313f8be7908bc20e5abe43f0837" @@ -2369,6 +2408,11 @@ busboy@^1.0.0: dependencies: streamsearch "^1.1.0" +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -2428,6 +2472,11 @@ caniuse-lite@^1.0.30001688: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz#00c30a2fc11e3c98c25e5125418752af3ae2f49f" integrity sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ== +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2468,10 +2517,10 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chownr@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" - integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chrome-trace-event@^1.0.2: version "1.0.4" @@ -2550,7 +2599,12 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2613,6 +2667,11 @@ consola@^3.2.3: resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.0.tgz#4cfc9348fd85ed16a17940b3032765e31061ab88" integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA== +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + content-disposition@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -2657,6 +2716,11 @@ cookiejar@^2.1.4: resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + core-util-is@^1.0.3, core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -2707,6 +2771,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2721,6 +2792,13 @@ debug@3.1.0: dependencies: ms "2.0.0" +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + debug@4.3.6: version "4.3.6" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" @@ -2728,13 +2806,6 @@ debug@4.3.6: dependencies: ms "2.1.2" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" @@ -2786,6 +2857,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -2796,6 +2872,11 @@ destroy@1.2.0, destroy@^1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2833,6 +2914,14 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -3168,6 +3257,11 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + external-editor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -3177,6 +3271,16 @@ external-editor@^3.1.0: iconv-lite "^0.4.24" tmp "^0.0.33" +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3337,6 +3441,11 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + fork-ts-checker-webpack-plugin@9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz#c12c590957837eb02b02916902dcf3e675fd2b1e" @@ -3360,6 +3469,16 @@ form-data-encoder@^2.1.2: resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== +form-data@^2.5.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.2.tgz#dc653743d1de2fcc340ceea38079daf6e9069fd2" + integrity sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + safe-buffer "^5.2.1" + form-data@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" @@ -3369,6 +3488,15 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + formidable@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.2.tgz#207c33fecdecb22044c82ba59d0c63a12fb81d77" @@ -3402,6 +3530,13 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs-monkey@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" @@ -3422,6 +3557,21 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3474,6 +3624,13 @@ get-stream@^9.0.1: "@sec-ant/readable-stream" "^0.4.1" is-stream "^4.0.1" +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3505,18 +3662,6 @@ glob@11.0.1: package-json-from-dist "^1.0.0" path-scurry "^2.0.0" -glob@^10.3.7: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -3576,6 +3721,19 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -3591,6 +3749,11 @@ has-symbols@^1.1.0: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -3624,6 +3787,15 @@ http-errors@2.0.0, http-errors@^2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + http2-wrapper@^2.1.10: version "2.2.1" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" @@ -3632,6 +3804,14 @@ http2-wrapper@^2.1.10: quick-lru "^5.1.1" resolve-alpn "^1.2.0" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -3785,6 +3965,11 @@ is-stream@^4.0.1: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-4.0.1.tgz#375cf891e16d2e4baec250b85926cffc14720d9b" integrity sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" @@ -3805,6 +3990,11 @@ isomorphic-ws@^5.0.0: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -3863,15 +4053,6 @@ iterare@1.2.1: resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - jackspeak@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" @@ -4256,10 +4437,10 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -jose@^5.9.6: - version "5.9.6" - resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883" - integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ== +jose@^4.15.9: + version "4.15.9" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" + integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== js-tokens@^4.0.0: version "4.0.0" @@ -4281,10 +4462,10 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsep@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.4.0.tgz#19feccbfa51d8a79f72480b4b8e40ce2e17152f0" - integrity sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw== +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== jsesc@^3.0.2: version "3.1.0" @@ -4311,11 +4492,21 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -4335,14 +4526,20 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonpath-plus@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-10.2.0.tgz#84d680544d9868579cc7c8f59bbe153a5aad54c4" - integrity sha512-T9V+8iNYKFL2n2rF+w02LBOT2JjDnTjioaNFrxRy0Bv1y/hNsqR/EBK7Ojy2ythRHwmz2cRIls+9JitQGZC/sw== +jsonpath-plus@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz#7ad94e147b3ed42f7939c315d2b9ce490c5a3899" + integrity sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA== + +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: - "@jsep-plugin/assignment" "^1.3.0" - "@jsep-plugin/regex" "^1.0.4" - jsep "^1.4.0" + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" @@ -4426,11 +4623,6 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - lru-cache@^11.0.0: version "11.0.2" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39" @@ -4443,6 +4635,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + magic-string@0.30.12: version "0.30.12" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" @@ -4457,6 +4656,13 @@ magic-string@0.30.17: dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -4536,7 +4742,7 @@ mime-db@^1.28.0, mime-db@^1.53.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -4603,23 +4809,30 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^4.0.0: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: +minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -minizlib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" - integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== dependencies: - minipass "^7.0.4" - rimraf "^5.0.5" + minipass "^3.0.0" + yallist "^4.0.0" mkdirp@^0.5.4: version "0.5.6" @@ -4628,10 +4841,10 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" -mkdirp@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" - integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== ms@2.0.0: version "2.0.0" @@ -4691,6 +4904,11 @@ node-abort-controller@^3.0.1: resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-emoji@1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -4698,7 +4916,7 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.9: +node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -4715,6 +4933,13 @@ node-releases@^2.0.19: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -4732,10 +4957,20 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -oauth4webapi@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-3.1.4.tgz#50695385cea8e7a43f3e2e23bc33ea27faece4a7" - integrity sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg== +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== oauth@0.10.x: version "0.10.0" @@ -4752,11 +4987,21 @@ object-hash@3.0.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-inspect@^1.13.3: version "1.13.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== +oidc-token-hash@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6" + integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw== + on-finished@2.4.1, on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -4778,13 +5023,15 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -openid-client@^6.1.3: - version "6.1.7" - resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-6.1.7.tgz#cb23cbfc1a37690ae553ab72505605d8660da057" - integrity sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg== +openid-client@^5.3.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.7.1.tgz#34cace862a3e6472ed7d0a8616ef73b7fb85a9c3" + integrity sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew== dependencies: - jose "^5.9.6" - oauth4webapi "^3.1.4" + jose "^4.15.9" + lru-cache "^6.0.0" + object-hash "^2.2.0" + oidc-token-hash "^5.0.3" optionator@^0.9.3: version "0.9.4" @@ -4942,14 +5189,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" @@ -4983,6 +5222,11 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -5069,7 +5313,14 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -punycode@^2.1.0: +psl@^1.1.28: + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -5093,6 +5344,11 @@ qs@^6.11.0: dependencies: side-channel "^1.1.0" +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5143,7 +5399,7 @@ readable-stream@^2.2.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.4.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5174,6 +5430,32 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5245,12 +5527,12 @@ rfc4648@^1.3.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.4.tgz#1174c0afba72423a0b70c386ecfeb80aa61b05ca" integrity sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg== -rimraf@^5.0.5: - version "5.0.10" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" - integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: - glob "^10.3.7" + glob "^7.1.3" router@^2.0.0: version "2.0.0" @@ -5279,7 +5561,7 @@ rxjs@7.8.1, rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5289,7 +5571,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -5332,7 +5614,7 @@ semver-truncate@^3.0.0: dependencies: semver "^7.3.5" -semver@^6.3.0, semver@^6.3.1: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -5377,6 +5659,11 @@ serve-static@^2.1.0: parseurl "^1.3.3" send "^1.0.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -5434,7 +5721,7 @@ side-channel@^1.0.6, side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" -signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -5528,6 +5815,21 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sshpk@^1.7.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -5577,7 +5879,7 @@ string-length@^4.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5742,17 +6044,17 @@ tar-stream@^3.1.7: fast-fifo "^1.2.0" streamx "^2.15.0" -tar@^7.0.0: - version "7.4.3" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" - integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== +tar@^6.1.11: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: - "@isaacs/fs-minipass" "^4.0.0" - chownr "^3.0.0" - minipass "^7.1.2" - minizlib "^3.0.1" - mkdirp "^3.0.1" - yallist "^5.0.0" + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" terser-webpack-plugin@^5.3.10: version "5.3.11" @@ -5796,13 +6098,6 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -tmp-promise@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" - integrity sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== - dependencies: - tmp "^0.2.0" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -5810,11 +6105,6 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -tmp@^0.2.0: - version "0.2.3" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" - integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== - tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -5840,6 +6130,14 @@ token-types@^6.0.0: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -5919,11 +6217,23 @@ tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.8.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.5.0, tslib@^2.6.2: +tslib@2.8.1, tslib@^2.1.0, tslib@^2.4.1, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -6002,6 +6312,11 @@ unbzip2-stream@^1.4.3: buffer "^5.2.1" through "^2.3.8" +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici-types@~6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" @@ -6042,6 +6357,11 @@ utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -6061,6 +6381,15 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -6142,6 +6471,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -6196,7 +6532,7 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@^8.18.0: +ws@^8.11.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== @@ -6221,10 +6557,10 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yallist@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" - integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^2.7.0: version "2.7.0" diff --git a/server/src/types.ts b/server/src/types.ts index a02d6446..7c8c82ec 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -142,7 +142,7 @@ export interface IApp { } - +//Migrated to templates export interface ITemplate { name: string, deploymentstrategy: 'git' | 'docker', @@ -191,7 +191,7 @@ export interface ISecurityContext { add: string[]; } } - +//Migrated to apps export interface IExtraVolume { name: string, mountPath: string, @@ -201,6 +201,7 @@ export interface IExtraVolume { accessModes: string[], } +//Migrated to apps export interface ICronjob { name: string, schedule: string, @@ -244,6 +245,7 @@ export interface IPipelineList { items: IPipeline[], } +//Migrated to apps export interface IGithubRepository { admin: boolean, description?: string, @@ -282,12 +284,15 @@ export interface IKubectlMetadata { uid?: string; finalizers?: [Array: Object]; } + +//Migrated to pipelines export interface IKubectlPipeline { apiVersion: string; kind: string; metadata: IKubectlMetadata, spec: IPipeline } +//Migrated to pipelines export interface IKubectlPipelineList { apiVersion: string; kind: string; @@ -302,6 +307,8 @@ export interface IKubectlApp metadata: IKubectlMetadata spec: IApp ; } + +//Migrated to templates export interface IKubectlTemplate { apiVersion: string; From ba268f82a5961a08b6a212c4716e883a475062c4 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 5 Feb 2025 20:51:55 +0100 Subject: [PATCH 14/65] improve logging, connect to kubernetes --- server-refactored-v3/.env.template | 55 ++++ server-refactored-v3/package.json | 3 +- server-refactored-v3/src/kubectl/kubectl.ts | 58 ++--- .../src/logger/logger.spec.ts | 7 + server-refactored-v3/src/logger/logger.ts | 25 ++ server-refactored-v3/src/main.ts | 9 +- .../src/settings/settings.module.ts | 3 +- .../src/settings/settings.service.ts | 6 + server-refactored-v3/yarn.lock | 244 ++++++++++-------- 9 files changed, 274 insertions(+), 136 deletions(-) create mode 100644 server-refactored-v3/.env.template create mode 100644 server-refactored-v3/src/logger/logger.spec.ts create mode 100644 server-refactored-v3/src/logger/logger.ts diff --git a/server-refactored-v3/.env.template b/server-refactored-v3/.env.template new file mode 100644 index 00000000..1891cf50 --- /dev/null +++ b/server-refactored-v3/.env.template @@ -0,0 +1,55 @@ +PORT=2000 +KUBERO_WEBHOOK_SECRET=mysecret +#KUBERO_USERS=W3tpZDoxLG1ldGhvZDpsb2NhbCx1c2VybmFtZTpxd2VyLHBhc3N3b3JkOnF3ZXIsYXBpdG9rZW46bkpaNVMxUzdkYng4YTZoalNVNG4saW5zZWN1cmU6dHJ1ZX1d +# user: qwer, password: qwer +# generate with: echo -n "[{"id":1,"method":"local","username":"qwer","password":"qwer","apitoken":"nJZ5S1S7dbx8a6hjSU4n","insecure":true}]" | base64 + +# webhook configuration, Must be a accessible from the internet +KUBERO_WEBHOOK_URL=https://kuberoXXXXXXXXXXXXX.loca.lt/api/repo/webhooks + +KUBECONFIG_PATH=./kubeconfig +#KUBECONFIG_BASE64=$(cat ./kubeconfig | base64 -w 0) + +KUBERO_CONFIG_PATH=./config.yaml +KUBERO_CONTEXT=kind-kubero-001 +KUBERO_NAMESPACE=kubero-dev # needs to be created manually in the cluster, since the in cluster default is "kubero" +KUBERO_SESSION_KEY=randomString +DEBUG=*.* +KUBERO_CLUSTERISSUER=letsencrypt-prod +KUBERO_BUILD_REGISTRY=kubero-registry-yourdomain.com/something + +KUBERO_PROMETHEUS_ENDPOINT=http://prometheus.localhost + +########################################## +# git repository configuration +# +#GITHUB_PERSONAL_ACCESS_TOKEN= + +#GITEA_PERSONAL_ACCESS_TOKEN= +#GITEA_BASEURL=http://localhost:3000 + +#GOGS_PERSONAL_ACCESS_TOKEN= +#GOGS_BASEURL=http://localhost:3000 + +#GITLAB_BASEURL=http://localhost:3080 +#GITLAB_PERSONAL_ACCESS_TOKEN=glpat- + +#BITBUCKET_USERNAME=XXXXXXXXX +#BITBUCKET_APP_PASSWORD= + + +################################################ +# authentication section +# +#GITHUB_CLIENT_SECRET= +#GITHUB_CLIENT_ID= +#GITHUB_CLIENT_CALLBACKURL=http://kubero.lacolhost.com/api/auth/github/callback +#GITHUB_CLIENT_ORG= + +#OAUTO2_CLIENT_NAME=Gitea +#OAUTO2_CLIENT_AUTH_URL=http://gitea.lacolhost.com:3000/login/oauth/authorize +#OAUTO2_CLIENT_TOKEN_URL=http://gitea.lacolhost.com:3000/login/oauth/access_token +#OAUTH2_CLIENT_ID= +#OAUTH2_CLIENT_SECRET= +#OAUTH2_CLIENT_CALLBACKURL=http://kubero.lacolhost.com/api/auth/oauth2/callback +#OAUTH2_CLIENT_SCOPE=openid profile email groups \ No newline at end of file diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index 84c200e5..ac0c0d10 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -21,7 +21,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@kubernetes/client-node": "^0.20.0", + "@kubernetes/client-node": "^0.22.3", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/passport": "^11.0.5", @@ -32,6 +32,7 @@ "@nestjs/websockets": "^11.0.7", "@types/bcrypt": "^5.0.2", "bcrypt": "^5.1.1", + "dotenv": "^16.4.7", "passport": "^0.7.0", "passport-github2": "^0.1.12", "passport-local": "^1.0.0", diff --git a/server-refactored-v3/src/kubectl/kubectl.ts b/server-refactored-v3/src/kubectl/kubectl.ts index 15becd14..8d4ae2d2 100644 --- a/server-refactored-v3/src/kubectl/kubectl.ts +++ b/server-refactored-v3/src/kubectl/kubectl.ts @@ -79,7 +79,7 @@ export class Kubectl { this.kc.loadFromCluster(); this.logger.debug("ℹ️ Kubeconfig loaded from cluster"); } catch (error) { - this.logger.debug("❌ Error loading from cluster"); + this.logger.error("❌ Error loading from cluster"); //this.logger.debug(error); } } @@ -97,7 +97,7 @@ export class Kubectl { this.exec = new Exec(this.kc) this.customObjectsApi = this.kc.makeApiClient(CustomObjectsApi); } catch (error) { - console.log("❌ Error creating api clients. Check kubeconfig, cluster connectivity and context"); + this.logger.error("❌ Error creating api clients. Check kubeconfig, cluster connectivity and context"); //this.logger.debug(error); this.kubeVersion = void 0; return; @@ -106,15 +106,15 @@ export class Kubectl { this.getKubeVersion() .then(v => { if (v && v.gitVersion) { - console.log("ℹ️ Kube version: " + v.gitVersion); + this.logger.debug("ℹ️ Kube version: " + v.gitVersion); } else { - console.log("❌ Failed to get Kubernetes version"); + this.logger.error("❌ Failed to get Kubernetes version"); process.env.KUBERO_SETUP = 'enabled'; } this.kubeVersion = v; }) .catch(error => { - console.log("❌ Failed to get Kubernetes version"); + this.logger.error("❌ Failed to get Kubernetes version"); //this.logger.debug(error); }); @@ -721,8 +721,8 @@ export class Kubectl { ret.push(storageClass); } } catch (error) { - console.log(error); - console.log('ERROR fetching storageclasses'); + this.logger.error(error); + this.logger.error('ERROR fetching storageclasses'); } return ret; } @@ -742,8 +742,8 @@ export class Kubectl { ret.push(ingressClass); } } catch (error) { - console.log(error); - console.log('ERROR fetching ingressclasses'); + this.logger.error(error); + this.logger.error('ERROR fetching ingressclasses'); } return ret; } @@ -755,7 +755,7 @@ export class Kubectl { await new Promise(resolve => setTimeout(resolve, 1000)); } catch (error) { //console.log(error); - console.log('ERROR deleting job: '+name+' ' +namespace); + this.logger.error('ERROR deleting job: '+name+' ' +namespace); } } @@ -811,8 +811,8 @@ export class Kubectl { try { return await this.batchV1Api.createNamespacedJob(namespace, job); } catch (error) { - console.log(error); - console.log('ERROR creating Repo scan job: '+app+' ' +namespace); + this.logger.error(error); + this.logger.error('ERROR creating Repo scan job: '+app+' ' +namespace); } } @@ -894,8 +894,8 @@ export class Kubectl { try { return await this.batchV1Api.createNamespacedJob(namespace, job); } catch (error) { - console.log(error); - console.log('ERROR creating Image scan job'); + this.logger.error(error); + this.logger.error('ERROR creating Image scan job'); } } @@ -905,8 +905,8 @@ export class Kubectl { const logs = await this.coreV1Api.readNamespacedPodLog(logPod, namespace, undefined, false); return logs.body; } catch (error) { - console.log(error); - console.log('ERROR fetching scan logs'); + this.logger.error(error); + this.logger.error('ERROR fetching scan logs'); } } @@ -938,15 +938,15 @@ export class Kubectl { //return latestPod?.metadata?.name } catch (error) { - console.log(error); - console.log('ERROR fetching pod by label'); + this.logger.error(error); + this.logger.error('ERROR fetching pod by label'); } } public async deployApp(namespace: string, appName: string, tag: string) { let deploymentName = appName+'-kuberoapp-web'; - console.log("deploy app: " + appName, ",namespace: " + namespace, ",tag: " + tag, ",deploymentName: " + deploymentName); + this.logger.error("deploy app: " + appName, ",namespace: " + namespace, ",tag: " + tag, ",deploymentName: " + deploymentName); // format : https://jsonpatch.com/ const patch = [ @@ -1134,13 +1134,13 @@ export class Kubectl { job.spec.template.spec.initContainers[2].env[2].value = dockerfilePath; } - console.log("create build job: " + job); + this.logger.log("create build job: " + job); try { return await this.batchV1Api.createNamespacedJob(namespace, job); } catch (error) { - console.log(error); - console.log('ERROR creating build job'); + this.logger.error(error); + this.logger.error('ERROR creating build job'); } } @@ -1186,11 +1186,11 @@ export class Kubectl { try { const versionApi = kc.makeApiClient(VersionApi); let versionInfo = await versionApi.getCode() - console.log(JSON.stringify(versionInfo.body)); + this.logger.debug(JSON.stringify(versionInfo.body)); return { error: null, valid: true }; } catch (error: any) { - console.log("Error validating kubeconfig: " + error); - console.log(error); + this.logger.error("Error validating kubeconfig: " + error); + this.logger.error(error); return {error: error.message, valid: false}; } } @@ -1202,9 +1202,9 @@ export class Kubectl { this.kc.setCurrentContext(kubeContext); */ this.initKubeConfig(); - console.log(kubeContext, this.kc.getCurrentContext()); + this.logger.debug(kubeContext, this.kc.getCurrentContext()); - console.log("Kubeconfig updated"); + this.logger.log("Kubeconfig updated"); } public async checkNamespace(namespace: string): Promise { @@ -1243,7 +1243,7 @@ export class Kubectl { ); return true; } catch (error) { - console.log(error); + this.logger.error(error); return false; } } @@ -1260,7 +1260,7 @@ export class Kubectl { return await this.coreV1Api.createNamespace(ns); } catch (error) { //console.log(error); - console.log('ERROR creating namespace'); + this.logger.error('ERROR creating namespace'); } } diff --git a/server-refactored-v3/src/logger/logger.spec.ts b/server-refactored-v3/src/logger/logger.spec.ts new file mode 100644 index 00000000..d4966dab --- /dev/null +++ b/server-refactored-v3/src/logger/logger.spec.ts @@ -0,0 +1,7 @@ +import { Logger } from './logger'; + +describe('Logger', () => { + it('should be defined', () => { + expect(new Logger()).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/logger/logger.ts b/server-refactored-v3/src/logger/logger.ts new file mode 100644 index 00000000..9a6cf135 --- /dev/null +++ b/server-refactored-v3/src/logger/logger.ts @@ -0,0 +1,25 @@ +import { ConsoleLogger } from '@nestjs/common' + +/** + * A custom logger that disables all logs emitted by calling `log` method if + * they use one of the following contexts: + * - `InstanceLoader` + * - `RoutesResolver` + * - `RouterExplorer` + * - `NestFactory` + */ +export class CustomConsoleLogger extends ConsoleLogger { + static contextsToIgnore = [ + 'InstanceLoader', + 'RoutesResolver', + 'RouterExplorer', + //'NestFactory', // I prefer not including this one + ] + + log(_: any, context?: string): void { + context = context || '' + if (!CustomConsoleLogger.contextsToIgnore.includes(context)) { + super.log.apply(this, arguments) + } + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 17d11b7c..40c0d430 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -1,11 +1,14 @@ import { NestFactory } from '@nestjs/core'; -import { Logger, ConsoleLogger } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; +import { CustomConsoleLogger } from './logger/logger'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; +import * as dotenv from 'dotenv'; +dotenv.config(); async function bootstrap() { const app = await NestFactory.create(AppModule, { - logger: new ConsoleLogger({ + logger: new CustomConsoleLogger({ prefix: 'Kubero', //logLevels: ['log', 'error', 'warn', 'debug', 'verbose'], }), @@ -48,6 +51,6 @@ async function bootstrap() { await app.listen(process.env.PORT ?? 2000); // Use port 2000 for compatibility with kubero v2 - Logger.warn(`⚡️[server]: Server is running at: ${await app.getUrl()}`, 'Bootstrap'); + Logger.log(`⚡️[server]: Server is running at: ${await app.getUrl()}`, 'Bootstrap'); } bootstrap(); diff --git a/server-refactored-v3/src/settings/settings.module.ts b/server-refactored-v3/src/settings/settings.module.ts index 8f2cf65a..b13c7675 100644 --- a/server-refactored-v3/src/settings/settings.module.ts +++ b/server-refactored-v3/src/settings/settings.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; import { SettingsController } from './settings.controller'; import { SettingsService } from './settings.service'; +import { Kubectl } from '../kubectl/kubectl'; @Module({ controllers: [SettingsController], - providers: [SettingsService] + providers: [SettingsService, Kubectl] }) export class SettingsModule {} diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 8c26300c..93337272 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -1,6 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { IKuberoConfig } from './settings.interface'; import { KuberoConfig } from './kubero-config/kubero-config'; +import { Kubectl } from 'src/kubectl/kubectl'; import { readFileSync, writeFileSync } from 'fs'; import YAML from 'yaml' import { join } from 'path'; @@ -9,6 +10,11 @@ import { join } from 'path'; export class SettingsService { private readonly logger = new Logger(SettingsService.name); + constructor() { + console.log('SettingsService constructor') + + } + // Load settings from a file or from kubernetes async getSettings(): Promise { diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 84e1ad93..32e386b0 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -592,6 +592,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -848,27 +855,33 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@kubernetes/client-node@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.20.0.tgz#4447ae27fd6eef3d4830a5a039f3b84ffd5c5913" - integrity sha512-xxlv5GLX4FVR/dDKEsmi4SPeuB49aRc35stndyxcC73XnUEEwF39vXbROpHOirmDse8WE9vxOjABnSVS+jb7EA== +"@jsep-plugin/assignment@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@jsep-plugin/assignment/-/assignment-1.3.0.tgz#fcfc5417a04933f7ceee786e8ab498aa3ce2b242" + integrity sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ== + +"@jsep-plugin/regex@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@jsep-plugin/regex/-/regex-1.0.4.tgz#cb2fc423220fa71c609323b9ba7f7d344a755fcc" + integrity sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg== + +"@kubernetes/client-node@^0.22.3": + version "0.22.3" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.22.3.tgz#0b38f4be09ab28b73b31bcf2df680f9c899242b8" + integrity sha512-dG8uah3+HDJLpJEESshLRZlAZ4PgDeV9mZXT0u1g7oy4KMRzdZ7n5g0JEIlL6QhK51/2ztcIqURAnjfjJt6Z+g== 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 "^7.2.0" + jsonpath-plus "^10.2.0" request "^2.88.0" rfc4648 "^1.3.0" stream-buffers "^3.0.2" - tar "^6.1.11" + tar "^7.0.0" tslib "^2.4.1" - ws "^8.11.0" + ws "^8.18.0" optionalDependencies: - openid-client "^5.3.0" + openid-client "^6.1.3" "@lukeed/csprng@^1.0.0": version "1.1.0" @@ -1146,6 +1159,11 @@ dependencies: consola "^3.2.3" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/core@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" @@ -1366,11 +1384,6 @@ "@types/connect" "*" "@types/node" "*" -"@types/caseless@*": - version "0.12.5" - resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5" - integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg== - "@types/connect@*": version "3.4.38" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" @@ -1475,11 +1488,6 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/js-yaml@^4.0.1": - version "4.0.9" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" - integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== - "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1502,13 +1510,6 @@ dependencies: undici-types "~6.20.0" -"@types/node@^20.1.1": - version "20.17.16" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.16.tgz#b33b0edc1bf925b27349e494b871ca4451fabab4" - integrity sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw== - dependencies: - undici-types "~6.19.2" - "@types/oauth@*": version "0.9.6" resolved "https://registry.yarnpkg.com/@types/oauth/-/oauth-0.9.6.tgz#fb5a278f6a826108a7467a01f856324e11e9ba4a" @@ -1568,16 +1569,6 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/request@^2.47.1": - version "2.48.12" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" - integrity sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw== - dependencies: - "@types/caseless" "*" - "@types/node" "*" - "@types/tough-cookie" "*" - form-data "^2.5.0" - "@types/send@*": version "0.17.4" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" @@ -1618,18 +1609,6 @@ "@types/methods" "^1.1.4" "@types/superagent" "^8.1.0" -"@types/tough-cookie@*": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" - integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== - -"@types/ws@^8.5.3": - version "8.5.14" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.14.tgz#93d44b268c9127d96026cf44353725dd9b6c3c21" - integrity sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw== - dependencies: - "@types/node" "*" - "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -2522,6 +2501,11 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + chrome-trace-event@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" @@ -2900,6 +2884,11 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -3469,16 +3458,6 @@ form-data-encoder@^2.1.2: resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== -form-data@^2.5.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.2.tgz#dc653743d1de2fcc340ceea38079daf6e9069fd2" - integrity sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - safe-buffer "^5.2.1" - form-data@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" @@ -3662,6 +3641,18 @@ glob@11.0.1: package-json-from-dist "^1.0.0" path-scurry "^2.0.0" +glob@^10.3.7: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -4053,6 +4044,15 @@ iterare@1.2.1: resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jackspeak@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" @@ -4437,10 +4437,10 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -jose@^4.15.9: - version "4.15.9" - resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" - integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== +jose@^5.9.6: + version "5.9.6" + resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883" + integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ== js-tokens@^4.0.0: version "4.0.0" @@ -4467,6 +4467,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +jsep@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.4.0.tgz#19feccbfa51d8a79f72480b4b8e40ce2e17152f0" + integrity sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw== + jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" @@ -4526,10 +4531,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonpath-plus@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz#7ad94e147b3ed42f7939c315d2b9ce490c5a3899" - integrity sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA== +jsonpath-plus@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-10.2.0.tgz#84d680544d9868579cc7c8f59bbe153a5aad54c4" + integrity sha512-T9V+8iNYKFL2n2rF+w02LBOT2JjDnTjioaNFrxRy0Bv1y/hNsqR/EBK7Ojy2ythRHwmz2cRIls+9JitQGZC/sw== + dependencies: + "@jsep-plugin/assignment" "^1.3.0" + "@jsep-plugin/regex" "^1.0.4" + jsep "^1.4.0" jsprim@^1.2.2: version "1.4.2" @@ -4623,6 +4632,11 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^11.0.0: version "11.0.2" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39" @@ -4635,13 +4649,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - magic-string@0.30.12: version "0.30.12" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" @@ -4821,7 +4828,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -minipass@^7.1.2: +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== @@ -4834,6 +4841,14 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +minizlib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== + dependencies: + minipass "^7.0.4" + rimraf "^5.0.5" + mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -4846,6 +4861,11 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4972,6 +4992,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +oauth4webapi@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-3.1.4.tgz#50695385cea8e7a43f3e2e23bc33ea27faece4a7" + integrity sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg== + oauth@0.10.x: version "0.10.0" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.0.tgz#3551c4c9b95c53ea437e1e21e46b649482339c58" @@ -4987,21 +5012,11 @@ object-hash@3.0.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== -object-hash@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" - integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== - object-inspect@^1.13.3: version "1.13.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== -oidc-token-hash@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6" - integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw== - on-finished@2.4.1, on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -5023,15 +5038,13 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -openid-client@^5.3.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.7.1.tgz#34cace862a3e6472ed7d0a8616ef73b7fb85a9c3" - integrity sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew== +openid-client@^6.1.3: + version "6.1.7" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-6.1.7.tgz#cb23cbfc1a37690ae553ab72505605d8660da057" + integrity sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg== dependencies: - jose "^4.15.9" - lru-cache "^6.0.0" - object-hash "^2.2.0" - oidc-token-hash "^5.0.3" + jose "^5.9.6" + oauth4webapi "^3.1.4" optionator@^0.9.3: version "0.9.4" @@ -5189,6 +5202,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" @@ -5534,6 +5555,13 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^5.0.5: + version "5.0.10" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== + dependencies: + glob "^10.3.7" + router@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/router/-/router-2.0.0.tgz#8692720b95de83876870d7bc638dd3c7e1ae8a27" @@ -5561,7 +5589,7 @@ rxjs@7.8.1, rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6056,6 +6084,18 @@ tar@^6.1.11: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^7.0.0: + version "7.4.3" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" + integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.0.1" + mkdirp "^3.0.1" + yallist "^5.0.0" + terser-webpack-plugin@^5.3.10: version "5.3.11" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz#93c21f44ca86634257cac176f884f942b7ba3832" @@ -6312,11 +6352,6 @@ unbzip2-stream@^1.4.3: buffer "^5.2.1" through "^2.3.8" -undici-types@~6.19.2: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== - undici-types@~6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" @@ -6532,7 +6567,7 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@^8.11.0: +ws@^8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== @@ -6562,6 +6597,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + yaml@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98" From a3a2a290d6b07981d461e2b7bfb2d4f75d16ec7a Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 5 Feb 2025 23:17:53 +0100 Subject: [PATCH 15/65] make settings readable --- server-refactored-v3/README.md | 53 +-- server-refactored-v3/config.yaml.example | 379 ++++++++++++++++++ server-refactored-v3/src/app.controller.ts | 1 + server-refactored-v3/src/app.service.ts | 2 + server-refactored-v3/src/logger/logger.ts | 2 + .../src/settings/settings.service.ts | 180 ++++++++- 6 files changed, 550 insertions(+), 67 deletions(-) create mode 100644 server-refactored-v3/config.yaml.example diff --git a/server-refactored-v3/README.md b/server-refactored-v3/README.md index c35976cb..6933ddad 100644 --- a/server-refactored-v3/README.md +++ b/server-refactored-v3/README.md @@ -1,30 +1,6 @@ -

- Nest Logo -

- -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest - -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Coverage -Discord -Backers on Open Collective -Sponsors on Open Collective - Donate us - Support us - Follow us on Twitter -

- - ## Description -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. +This is Kubero server part of the Kubero project. It is a NestJS application that provides REST API for the Kubero project. It is a part of the Kubero project that is a platform for managing Kubernetes clusters. ## Project setup @@ -58,19 +34,6 @@ $ yarn run test:e2e $ yarn run test:cov ``` -## Deployment - -When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. - -If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: - -```bash -$ yarn install -g mau -$ mau deploy -``` - -With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. - ## Resources Check out a few resources that may come in handy when working with NestJS: @@ -83,17 +46,3 @@ Check out a few resources that may come in handy when working with NestJS: - Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). - To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). - Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). - -## Support - -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). - -## Stay in touch - -- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) - -## License - -Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/server-refactored-v3/config.yaml.example b/server-refactored-v3/config.yaml.example new file mode 100644 index 00000000..777b76c9 --- /dev/null +++ b/server-refactored-v3/config.yaml.example @@ -0,0 +1,379 @@ +kubero: + readonly: false + console: + enabled: true + admin: + disabled: false + banner: + show: false + message: "Welcome to Kubero!" + bgcolor: "#8560a963" + fontcolor: "#00000087" + defaultannotations: + apps: + pipelines: + - janitor/ttl=5m +clusterissuer: letsencrypt-prod +templates: + enabled: true + catalogs: + - name: "Kubero" + description: "Kubero templates" + templateBasePath: "https://raw.githubusercontent.com/kubero-dev/kubero/main/services/" + index: + url: "https://raw.githubusercontent.com/kubero-dev/templates/main/index.json" + format: "json" # json or yaml # TODO has no effect yet. json is always used + - name: "Kubero Frameworks" + description: "Kubero templates" + templateBasePath: "https://raw.githubusercontent.com/kubero-dev/kubero/main/services/" + index: + url: "https://raw.githubusercontent.com/kubero-dev/templates/main/index-frameworks.json" + format: "json" # json or yaml # TODO has no effect yet. json is always used +notifications: + - name: "Slack" + enabled: false + type: "slack" + config: + url: "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX" + channel: "#kubero" + - name: "Webhook" + enabled: false + type: "webhook" + config: + url: "https://example.com/webhook" + secret: "XXXXX" + - name: "Discord" + enabled: false + type: "discord" + config: + url: "https://discord.com/api/webhooks/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX" + channel: "#kubero" +buildpacks: + - name: NodeJS + language: JavaScript + fetch: + repository: ghcr.io/kubero-dev/buildpacks/fetch + tag: v1.2 + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + build: + repository: node + tag: latest + command: "npm install" + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + run: + repository: node + tag: latest + command: "node index.js" + readOnlyAppStorage: true + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + - name: PHP + language: PHP + fetch: + repository: ghcr.io/kubero-dev/buildpacks/fetch + tag: v1.2 + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + build: + repository: composer + tag: latest + command: "composer install; chown -R 1000:1000 /app" + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + run: + repository: ghcr.io/kubero-dev/buildpacks/php + tag: "main" + command: "apache2-foreground" + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + allowPrivilegeEscalation: true + readOnlyRootFilesystem: false + capabilities: + add: [] + drop: [] + - name: Python + language: Python + fetch: + repository: ghcr.io/kubero-dev/buildpacks/fetch + tag: v1.2 + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + build: + repository: python + tag: 3.10-buster + command: "python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt" + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + run: + repository: python + tag: 3.10-buster + command: ". .venv/bin/activate && python3 main.py" + readOnlyAppStorage: true + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + - name: GoLang + language: GoLang + fetch: + repository: ghcr.io/kubero-dev/buildpacks/fetch + tag: v1.2 + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + build: + repository: golang + tag: alpine + command: "go mod download && go mod verify && go build -v -o app" + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + run: + repository: golang + tag: alpine + command: "./app" + readOnlyAppStorage: true + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + - name: Hugo + language: GoLang + fetch: + repository: ghcr.io/kubero-dev/buildpacks/fetch + tag: v1.2 + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + build: + repository: klakegg/hugo + tag: latest + command: hugo -D + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + run: + repository: caddy + tag: latest + command: caddy file-server --listen :8080 --root /app/public + readOnlyAppStorage: true + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + - name: Ruby + language: Ruby + fetch: + repository: ghcr.io/kubero-dev/buildpacks/fetch + tag: v1.2 + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + build: + repository: ruby + tag: "2.7" + command: "export GEM_HOME=/app/bundle; bundle install --jobs=4 --retry=3" + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + run: + repository: ruby + tag: "2.7" + command: "export GEM_HOME=/app/bundle; bundle exec ruby main.rb" + readOnlyAppStorage: true + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + - name: Static + language: HTML + fetch: + repository: ghcr.io/kubero-dev/buildpacks/fetch + tag: v1.2 + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + build: + repository: busybox + tag: latest + command: "echo 'Buildpack not required'" + readOnlyAppStorage: false + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] + run: + repository: caddy + tag: latest + command: caddy file-server --listen :8080 --root /app + readOnlyAppStorage: true + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + add: [] + drop: [] +podSizeList: +- name: small + description: 'Small (CPU: 0.25, Memory: 0.5Gi)' + default: true + resources: + requests: + memory: 0.5Gi + cpu: 250m + limits: + memory: 1Gi + cpu: 500m +- name: medium + description: 'Medium (CPU: 1, Memory: 2Gi)' + resources: + requests: + memory: 2Gi + cpu: 1000m + limits: + memory: 4Gi + cpu: 2000m +- name: large + description: 'Large (CPU: 2, Memory: 4Gi)' + active: false + resources: + requests: + memory: 4Gi + cpu: 2000m diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts index d36c6e74..e4b6c09c 100644 --- a/server-refactored-v3/src/app.controller.ts +++ b/server-refactored-v3/src/app.controller.ts @@ -7,6 +7,7 @@ import { AuthGuard } from '@nestjs/passport'; export class AppController { constructor(private readonly appService: AppService) {} +/* @UseGuards(AuthGuard('local')) @Get('/hello') getHello(): string { diff --git a/server-refactored-v3/src/app.service.ts b/server-refactored-v3/src/app.service.ts index 927d7cca..46ecab9f 100644 --- a/server-refactored-v3/src/app.service.ts +++ b/server-refactored-v3/src/app.service.ts @@ -2,7 +2,9 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { + /* getHello(): string { return 'Hello World!'; } + */ } diff --git a/server-refactored-v3/src/logger/logger.ts b/server-refactored-v3/src/logger/logger.ts index 9a6cf135..d360d215 100644 --- a/server-refactored-v3/src/logger/logger.ts +++ b/server-refactored-v3/src/logger/logger.ts @@ -14,6 +14,8 @@ export class CustomConsoleLogger extends ConsoleLogger { 'RoutesResolver', 'RouterExplorer', //'NestFactory', // I prefer not including this one + //'NestApplication', + //'WebSocketsController', ] log(_: any, context?: string): void { diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 93337272..dc48ff0f 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { IKuberoConfig } from './settings.interface'; import { KuberoConfig } from './kubero-config/kubero-config'; -import { Kubectl } from 'src/kubectl/kubectl'; +import { Kubectl } from '../kubectl/kubectl'; import { readFileSync, writeFileSync } from 'fs'; import YAML from 'yaml' import { join } from 'path'; @@ -9,33 +9,69 @@ import { join } from 'path'; @Injectable() export class SettingsService { private readonly logger = new Logger(SettingsService.name); + private runningConfig: IKuberoConfig - constructor() { - console.log('SettingsService constructor') - + constructor(private readonly kubectl: Kubectl) { + this.reloadRunningConfig() } // Load settings from a file or from kubernetes async getSettings(): Promise { - // TODO: Check if Kubero Administation is disabled + + if (this.checkAdminDisabled()) { + return new KuberoConfig(new Object() as IKuberoConfig) + } + + const configMap = new KuberoConfig(await this.readConfig()) + let config: any = {} + config.settings = configMap + + config["secrets"] = { + GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PERSONAL_ACCESS_TOKEN || '', + GITEA_PERSONAL_ACCESS_TOKEN: process.env.GITEA_PERSONAL_ACCESS_TOKEN || '', + GITEA_BASEURL: process.env.GITEA_BASEURL || '', + GITLAB_PERSONAL_ACCESS_TOKEN: process.env.GITLAB_PERSONAL_ACCESS_TOKEN || '', + GITLAB_BASEURL: process.env.GITLAB_BASEURL || '', + BITBUCKET_APP_PASSWORD: process.env.BITBUCKET_APP_PASSWORD || '', + BITBUCKET_USERNAME: process.env.BITBUCKET_USERNAME || '', + GOGS_PERSONAL_ACCESS_TOKEN: process.env.GOGS_PERSONAL_ACCESS_TOKEN || '', + GOGS_BASEURL: process.env.GOGS_BASEURL || '', + KUBERO_WEBHOOK_SECRET: process.env.KUBERO_WEBHOOK_SECRET || '', + GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET || '', + OAUTH2_CLIENT_SECRET: process.env.OAUTH2_CLIENT_SECRET || '', + } + return config + } + + private reloadRunningConfig(): void { + + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + this.kubectl.getKuberoConfig(namespace).then((kuberoes) => { + this.runningConfig = kuberoes.spec + }).catch((error) => { + this.logger.error('Error reading kuberoes config') + this.logger.error(error) + }) + } + + private async readConfig(): Promise { - let configMap: KuberoConfig if (process.env.NODE_ENV === "production") { - configMap = new KuberoConfig(this.loadConfigFromKubernetes()) + return await this.readConfigFromKubernetes() } else { - configMap = new KuberoConfig(this.readConfig()) + return this.readConfigFromFS() } - - return configMap; } - private loadConfigFromKubernetes(): IKuberoConfig { - // TODO: Load config from kubernetes - return new Object() as IKuberoConfig + + private async readConfigFromKubernetes(): Promise { + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + let kuberoes = await this.kubectl.getKuberoConfig(namespace) + return kuberoes.spec.kubero.config } - - private readConfig(): IKuberoConfig { + + private readConfigFromFS(): IKuberoConfig { // read config from local filesystem (dev mode) //const path = join(__dirname, 'config.yaml') const path = process.env.KUBERO_CONFIG_PATH || join(__dirname, 'config.yaml') @@ -59,4 +95,118 @@ export class SettingsService { }); } + public async getDefaultRegistry(): Promise { + + 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 + } + + public async getDomains(): Promise { + let allIngress = await this.kubectl.getAllIngress() + let domains: string[] = [] + allIngress.forEach((ingress: any) => { + ingress.spec.rules.forEach((rule: any) => { + domains.push(rule.host) + }) + }) + return domains + } + + private checkAdminDisabled(): boolean { + 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 + } } From 74865758f3cec619c46d776f288d71371759502f Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 6 Feb 2025 23:17:15 +0100 Subject: [PATCH 16/65] add some settings endpoints --- server-refactored-v3/package.json | 11 +-- server-refactored-v3/src/app.module.ts | 4 +- server-refactored-v3/src/apps/app/app.ts | 2 +- .../src/apps/apps.interface.ts | 2 +- .../src/auth/auth.controller.ts | 12 ++- server-refactored-v3/src/auth/auth.module.ts | 4 +- server-refactored-v3/src/auth/auth.service.ts | 40 ++++++++- .../src/common/common.controller.spec.ts | 18 ---- .../src/common/common.controller.ts | 12 --- .../src/common/common.module.ts | 9 -- .../src/common/common.service.ts | 27 ------ server-refactored-v3/src/core/core.module.ts | 7 ++ .../core.service.spec.ts} | 10 +-- server-refactored-v3/src/core/core.service.ts | 4 + .../kubernetes.interface.ts} | 0 .../kubernetes.service.spec.ts} | 2 +- .../kubernetes.service.ts} | 20 +++-- .../src/pipelines/pipeline/pipeline.ts | 2 +- .../src/pipelines/pipelines.interface.ts | 2 +- .../src/settings/settings.controller.ts | 11 ++- .../src/settings/settings.module.ts | 2 +- .../src/settings/settings.service.ts | 82 ++++++++++++++++++- .../src/templates/template.ts | 2 +- .../src/templates/templates.interface.ts | 2 +- server/src/kubero.ts | 8 ++ 25 files changed, 195 insertions(+), 100 deletions(-) delete mode 100644 server-refactored-v3/src/common/common.controller.spec.ts delete mode 100644 server-refactored-v3/src/common/common.controller.ts delete mode 100644 server-refactored-v3/src/common/common.module.ts delete mode 100644 server-refactored-v3/src/common/common.service.ts create mode 100644 server-refactored-v3/src/core/core.module.ts rename server-refactored-v3/src/{common/common.service.spec.ts => core/core.service.spec.ts} (55%) create mode 100644 server-refactored-v3/src/core/core.service.ts rename server-refactored-v3/src/{kubectl/kubectl.interface.ts => kubernetes/kubernetes.interface.ts} (100%) rename server-refactored-v3/src/{kubectl/kubectl.spec.ts => kubernetes/kubernetes.service.spec.ts} (70%) rename server-refactored-v3/src/{kubectl/kubectl.ts => kubernetes/kubernetes.service.ts} (98%) diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index ac0c0d10..f5ccb305 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -1,10 +1,11 @@ { - "name": "server-refactored-v3", - "version": "0.0.1", - "description": "", - "author": "", + + "name": "kubero", + "description": "Heroku clone on Kubernetes", + "main": "index.js", + "author": "Gianni Carafa", + "license": "GPL-3.0", "private": true, - "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 3414b844..7d05caeb 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -5,7 +5,6 @@ import { AppService } from './app.service'; import { EventsModule } from './events/events.module'; import { ServeStaticModule } from '@nestjs/serve-static'; import { AuthModule } from './auth/auth.module'; -import { CommonModule } from './common/common.module'; import { AppsModule } from './apps/apps.module'; import { PipelinesModule } from './pipelines/pipelines.module'; import { VulnerabilitiesModule } from './vulnerabilities/vulnerabilities.module'; @@ -16,6 +15,7 @@ import { TemplatesModule } from './templates/templates.module'; import { MetricsModule } from './metrics/metrics.module'; import { LogsModule } from './logs/logs.module'; import { DeploymentsModule } from './deployments/deployments.module'; +import { CoreModule } from './core/core.module'; @Module({ @@ -23,9 +23,9 @@ import { DeploymentsModule } from './deployments/deployments.module'; ServeStaticModule.forRoot({ rootPath: join(__dirname, '..', 'dist', 'public'), }), + CoreModule, EventsModule, AuthModule, - CommonModule, AppsModule, PipelinesModule, VulnerabilitiesModule, diff --git a/server-refactored-v3/src/apps/app/app.ts b/server-refactored-v3/src/apps/app/app.ts index f5e12910..8d179ffb 100644 --- a/server-refactored-v3/src/apps/app/app.ts +++ b/server-refactored-v3/src/apps/app/app.ts @@ -5,7 +5,7 @@ import { IExtraVolume, } from '../apps.interface'; -import { IKubectlMetadata, IKubectlApp } from "../../kubectl/kubectl.interface"; +import { IKubectlMetadata, IKubectlApp } from "../../kubernetes/kubernetes.interface"; import { IAddon } from '../../addons/addons.interface'; import { ISecurityContext, IPodSize } from "../../settings/settings.interface" import { hashSync, genSaltSync } from 'bcrypt'; diff --git a/server-refactored-v3/src/apps/apps.interface.ts b/server-refactored-v3/src/apps/apps.interface.ts index 6dd10ce7..c2b71496 100644 --- a/server-refactored-v3/src/apps/apps.interface.ts +++ b/server-refactored-v3/src/apps/apps.interface.ts @@ -1,6 +1,6 @@ import { IAddon } from "../addons/addons.interface" import { IPodSize, ISecurityContext } from "../settings/settings.interface" -import { IKubectlMetadata } from "../kubectl/kubectl.interface" +import { IKubectlMetadata } from "../kubernetes/kubernetes.interface" export interface IApp { name: string, diff --git a/server-refactored-v3/src/auth/auth.controller.ts b/server-refactored-v3/src/auth/auth.controller.ts index f5140542..56e951f7 100644 --- a/server-refactored-v3/src/auth/auth.controller.ts +++ b/server-refactored-v3/src/auth/auth.controller.ts @@ -1,8 +1,9 @@ import { Controller, Request, UseGuards, Post, Get, Response } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; - +import { AuthService } from './auth.service'; @Controller({ path: 'api/auth', version: '1' }) export class AuthController { + constructor(private readonly authService: AuthService) {} @Post('login') async login(@Request() req) { @@ -20,6 +21,13 @@ export class AuthController { } as any); console.log("logged out") return res.send("logged out"); - } + } + + @Get('session') + async session(@Request() req, @Response() res) { + const {message, status} = this.authService.getSession(req); + res.status(status); + res.send(message); + } } diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index 63fe0ee9..a1516f8d 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -1,13 +1,15 @@ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; +import { Kubectl } from '../kubernetes/kubernetes.service'; import { UsersModule } from '../users/users.module'; +import { SettingsService } from '../settings/settings.service'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; import { AuthController } from './auth.controller'; @Module({ imports: [UsersModule, PassportModule], - providers: [AuthService, LocalStrategy], + providers: [AuthService, LocalStrategy, Kubectl, SettingsService], controllers: [AuthController], }) export class AuthModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/auth.service.ts b/server-refactored-v3/src/auth/auth.service.ts index 3f3f3f93..86832bc4 100644 --- a/server-refactored-v3/src/auth/auth.service.ts +++ b/server-refactored-v3/src/auth/auth.service.ts @@ -1,9 +1,15 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Request } from '@nestjs/common'; import { UsersService } from '../users/users.service'; +import { Kubectl } from '../kubernetes/kubernetes.service'; +import { SettingsService } from '../settings/settings.service'; @Injectable() export class AuthService { - constructor(private usersService: UsersService) {} + constructor( + private usersService: UsersService, + private kubectl: Kubectl, + private settingsService: SettingsService + ) {} async validateUser(username: string, pass: string): Promise { const user = await this.usersService.findOne(username); @@ -13,4 +19,34 @@ export class AuthService { } return null; } + + getSession(req: Request,): { message: any, status: number } { + + let isAuthenticated = false + let status = 200 +/* + if (auth.authentication === true) { + isAuthenticated = req.isAuthenticated() + if (!isAuthenticated) { + status = 401 + } + } +*/ + + let message = { + "isAuthenticated": isAuthenticated, + "version": process.env.npm_package_version, + "kubernetesVersion": this.kubectl.getKubeVersion(), + "operatorVersion": this.kubectl.getOperatorVersion(), + "buildPipeline": this.settingsService.getBuildpipelineEnabled(), + "templatesEnabled": this.settingsService.getTemplateEnabled(), + //"auditEnabled": req.app.locals.audit.getAuditEnabled(), + "adminDisabled": this.settingsService.checkAdminDisabled(), + "consoleEnabled": this.settingsService.getConsoleEnabled(), + "metricsEnabled": this.settingsService.getMetricsEnabled(), + "sleepEnabled": this.settingsService.getSleepEnabled(), + } + + return { message: message, status: status } + } } \ No newline at end of file diff --git a/server-refactored-v3/src/common/common.controller.spec.ts b/server-refactored-v3/src/common/common.controller.spec.ts deleted file mode 100644 index c2d36fb9..00000000 --- a/server-refactored-v3/src/common/common.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CommonController } from './common.controller'; - -describe('CommonController', () => { - let controller: CommonController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [CommonController], - }).compile(); - - controller = module.get(CommonController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/server-refactored-v3/src/common/common.controller.ts b/server-refactored-v3/src/common/common.controller.ts deleted file mode 100644 index 1d93509c..00000000 --- a/server-refactored-v3/src/common/common.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { CommonService } from './common.service'; - -@Controller({ path: 'api/', version: '1' }) -export class CommonController { - constructor(private readonly commonService: CommonService) {} - - @Get(['session', 'auth/session']) - getSession(): string { - return this.commonService.getSession(); - } -} \ No newline at end of file diff --git a/server-refactored-v3/src/common/common.module.ts b/server-refactored-v3/src/common/common.module.ts deleted file mode 100644 index d0bb7ce4..00000000 --- a/server-refactored-v3/src/common/common.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { CommonService } from './common.service'; -import { CommonController } from './common.controller'; - -@Module({ - providers: [CommonService], - controllers: [CommonController] -}) -export class CommonModule {} diff --git a/server-refactored-v3/src/common/common.service.ts b/server-refactored-v3/src/common/common.service.ts deleted file mode 100644 index 31bb89f1..00000000 --- a/server-refactored-v3/src/common/common.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class CommonService { - getSession(): any { - -/* - let session = { - "isAuthenticated": isAuthenticated, - "version": process.env.npm_package_version, - "kubernetesVersion": req.app.locals.kubero.getKubernetesVersion(), - "operatorVersion": req.app.locals.kubero.getOperatorVersion(), - "buildPipeline": req.app.locals.kubero.getBuildpipelineEnabled(), - "templatesEnabled": req.app.locals.kubero.getTemplateEnabled(), - "auditEnabled": req.app.locals.audit.getAuditEnabled(), - "adminDisabled": req.app.locals.kubero.getAdminDisabled(), - "consoleEnabled": req.app.locals.kubero.getConsoleEnabled(), - "metricsEnabled": req.app.locals.kubero.getMetricsEnabled(), - "sleepEnabled": req.app.locals.kubero.getSleepEnabled(), - } -*/ - let session = { - "isAuthenticated": false, - } - return session; - } -} diff --git a/server-refactored-v3/src/core/core.module.ts b/server-refactored-v3/src/core/core.module.ts new file mode 100644 index 00000000..b52ce8a8 --- /dev/null +++ b/server-refactored-v3/src/core/core.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { CoreService } from './core.service'; + +@Module({ + providers: [CoreService] +}) +export class CoreModule {} diff --git a/server-refactored-v3/src/common/common.service.spec.ts b/server-refactored-v3/src/core/core.service.spec.ts similarity index 55% rename from server-refactored-v3/src/common/common.service.spec.ts rename to server-refactored-v3/src/core/core.service.spec.ts index 6ea5a77e..7eeca6af 100644 --- a/server-refactored-v3/src/common/common.service.spec.ts +++ b/server-refactored-v3/src/core/core.service.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { CommonService } from './common.service'; +import { CoreService } from './core.service'; -describe('CommonService', () => { - let service: CommonService; +describe('CoreService', () => { + let service: CoreService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [CommonService], + providers: [CoreService], }).compile(); - service = module.get(CommonService); + service = module.get(CoreService); }); it('should be defined', () => { diff --git a/server-refactored-v3/src/core/core.service.ts b/server-refactored-v3/src/core/core.service.ts new file mode 100644 index 00000000..acbb46cc --- /dev/null +++ b/server-refactored-v3/src/core/core.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class CoreService {} diff --git a/server-refactored-v3/src/kubectl/kubectl.interface.ts b/server-refactored-v3/src/kubernetes/kubernetes.interface.ts similarity index 100% rename from server-refactored-v3/src/kubectl/kubectl.interface.ts rename to server-refactored-v3/src/kubernetes/kubernetes.interface.ts diff --git a/server-refactored-v3/src/kubectl/kubectl.spec.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.spec.ts similarity index 70% rename from server-refactored-v3/src/kubectl/kubectl.spec.ts rename to server-refactored-v3/src/kubernetes/kubernetes.service.spec.ts index 6aeb7133..38a7d382 100644 --- a/server-refactored-v3/src/kubectl/kubectl.spec.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.spec.ts @@ -1,4 +1,4 @@ -import { Kubectl } from './kubectl'; +import { Kubectl } from './kubernetes.service'; describe('Kubectl', () => { it('should be defined', () => { diff --git a/server-refactored-v3/src/kubectl/kubectl.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts similarity index 98% rename from server-refactored-v3/src/kubectl/kubectl.ts rename to server-refactored-v3/src/kubernetes/kubernetes.service.ts index 8d4ae2d2..92e59880 100644 --- a/server-refactored-v3/src/kubectl/kubectl.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -1,5 +1,5 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList} from './kubectl.interface'; +import { Global, Injectable, Logger } from '@nestjs/common'; +import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList} from './kubernetes.interface'; import { IPipeline, } from '../pipelines/pipelines.interface'; import { KubectlPipeline } from '../pipelines/pipeline/pipeline'; import { KubectlApp, App } from '../apps/app/app'; @@ -103,7 +103,7 @@ export class Kubectl { return; } - this.getKubeVersion() + this.loadKubeVersion() .then(v => { if (v && v.gitVersion) { this.logger.debug("ℹ️ Kube version: " + v.gitVersion); @@ -118,14 +118,18 @@ export class Kubectl { //this.logger.debug(error); }); - this.getOperatorVersion() + this.loadOperatorVersion() .then(v => { this.logger.debug("ℹ️ Operator version: " + v); this.kuberoOperatorVersion = v || 'unknown'; }) } - public async getKubeVersion(): Promise{ + public getKubeVersion(): VersionInfo | void { + return this.kubeVersion; + } + + public async loadKubeVersion(): Promise{ // TODO and WARNING: This does not respect the context set by the user! try { let versionInfo = await this.versionApi.getCode() @@ -137,7 +141,11 @@ export class Kubectl { } } - private async getOperatorVersion(): Promise { + public getOperatorVersion(): string | undefined { + return this.kuberoOperatorVersion + } + + private async loadOperatorVersion(): Promise { const contextName = this.getCurrentContext(); const namespace = "kubero-operator-system"; diff --git a/server-refactored-v3/src/pipelines/pipeline/pipeline.ts b/server-refactored-v3/src/pipelines/pipeline/pipeline.ts index 8c4ace18..5b84faed 100644 --- a/server-refactored-v3/src/pipelines/pipeline/pipeline.ts +++ b/server-refactored-v3/src/pipelines/pipeline/pipeline.ts @@ -7,7 +7,7 @@ import { } from '../pipelines.interface'; import { IBuildpack } from '../../settings/settings.interface'; -import { IKubectlMetadata } from '../../kubectl/kubectl.interface'; +import { IKubectlMetadata } from '../../kubernetes/kubernetes.interface'; export class Pipeline implements IPipeline { diff --git a/server-refactored-v3/src/pipelines/pipelines.interface.ts b/server-refactored-v3/src/pipelines/pipelines.interface.ts index 9784c3d1..ddfe62b3 100644 --- a/server-refactored-v3/src/pipelines/pipelines.interface.ts +++ b/server-refactored-v3/src/pipelines/pipelines.interface.ts @@ -1,6 +1,6 @@ import { IGithubRepository } from "src/apps/apps.interface"; import { IBuildpack } from "src/settings/settings.interface"; -import { IKubectlMetadata } from "../kubectl/kubectl.interface"; +import { IKubectlMetadata } from "../kubernetes/kubernetes.interface"; export interface IPipeline { name: string; diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts index d3d93823..16c1c390 100644 --- a/server-refactored-v3/src/settings/settings.controller.ts +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -3,7 +3,6 @@ import { Controller, Get } from '@nestjs/common'; import { SettingsService } from './settings.service'; @Controller({ path: 'api/settings', version: '1' }) -@Controller('settings') export class SettingsController { constructor(private readonly settingsService: SettingsService) {} @@ -11,4 +10,14 @@ export class SettingsController { async getSettings() { return this.settingsService.getSettings(); } + + @Get('/banner') + async getBanner() { + return this.settingsService.getBanner(); + } + + @Get('/domains') + async getDomains() { + return this.settingsService.getDomains(); + } } diff --git a/server-refactored-v3/src/settings/settings.module.ts b/server-refactored-v3/src/settings/settings.module.ts index b13c7675..f0df2a6c 100644 --- a/server-refactored-v3/src/settings/settings.module.ts +++ b/server-refactored-v3/src/settings/settings.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { SettingsController } from './settings.controller'; import { SettingsService } from './settings.service'; -import { Kubectl } from '../kubectl/kubectl'; +import { Kubectl } from '../kubernetes/kubernetes.service'; @Module({ controllers: [SettingsController], diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index dc48ff0f..5b2812c0 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { IKuberoConfig } from './settings.interface'; import { KuberoConfig } from './kubero-config/kubero-config'; -import { Kubectl } from '../kubectl/kubectl'; +import { Kubectl } from '../kubernetes/kubernetes.service'; import { readFileSync, writeFileSync } from 'fs'; import YAML from 'yaml' import { join } from 'path'; @@ -10,6 +10,21 @@ import { join } from 'path'; export class SettingsService { private readonly logger = new Logger(SettingsService.name); private runningConfig: IKuberoConfig + private features: {[key: string]: boolean} = { + sleep: false, + metrics: false, + /* suggested features + console: false, + logs: false, + audit: false, + notifications: false, + templates: false, + addons: false, + deployments: false, + security: false, + settings: false, + */ + } constructor(private readonly kubectl: Kubectl) { this.reloadRunningConfig() @@ -134,7 +149,19 @@ export class SettingsService { return domains } - private checkAdminDisabled(): boolean { + public async getBanner(): Promise { + let defaultbanner = { + show: false, + text: "", + bgcolor: "white", + fontcolor: "white" + } + + let banner = await this.runningConfig.kubero?.banner || defaultbanner; + return banner + } + + public checkAdminDisabled(): boolean { return this.runningConfig.kubero.admin?.disabled || false } @@ -209,4 +236,55 @@ export class SettingsService { return ret } + + getBuildpipelineEnabled(){ + return process.env.KUBERO_BUILD_REGISTRY ? process.env.KUBERO_BUILD_REGISTRY != undefined : false + } + + getTemplateEnabled(){ + return this.runningConfig.templates?.enabled || false + } + + getConsoleEnabled(){ + if (this.runningConfig.kubero?.console?.enabled == undefined) { + return false; + } + return this.runningConfig.kubero?.console?.enabled; + } + + setMetricsStatus(status: boolean) { + this.features.metrics = status + } + + getMetricsEnabled(): boolean{ + return this.features.metrics + } + + private async checkForZeropod(): Promise { + // This is a very basic check for Zeropod. It requires the namespace zeropod-system to be present. + // But it does not check if the Zeropod controller is complete and running. + let enabled = false + try { + const nsList = await this.kubectl.getNamespaces() + for (const ns of nsList) { + if (ns.metadata?.name == 'zeropod-system') { + enabled = true + } + } + } catch (error) { + this.logger.error('❌ getSleepEnabled: could not check for Zeropod') + return false + } + + return enabled + } + + private async runFeatureCheck() { + //this.features.sleep = this.config.sleep.enabled; + this.features.sleep = await this.checkForZeropod() + } + + public getSleepEnabled(): boolean { + return this.features.sleep + } } diff --git a/server-refactored-v3/src/templates/template.ts b/server-refactored-v3/src/templates/template.ts index 39ae700a..a78fd33a 100644 --- a/server-refactored-v3/src/templates/template.ts +++ b/server-refactored-v3/src/templates/template.ts @@ -1,7 +1,7 @@ import { ITemplate, IKubectlTemplate } from './templates.interface'; import { IApp, IExtraVolume, ICronjob } from '../apps/apps.interface'; import { IAddon } from '../addons/addons.interface'; -import { IKubectlMetadata } from '../kubectl/kubectl.interface'; +import { IKubectlMetadata } from '../kubernetes/kubernetes.interface'; export class Template implements ITemplate{ public name: string diff --git a/server-refactored-v3/src/templates/templates.interface.ts b/server-refactored-v3/src/templates/templates.interface.ts index 75ee57c3..6e44baf7 100644 --- a/server-refactored-v3/src/templates/templates.interface.ts +++ b/server-refactored-v3/src/templates/templates.interface.ts @@ -1,5 +1,5 @@ import { IExtraVolume, ICronjob } from '../apps/apps.interface'; -import { IKubectlMetadata } from '../kubectl/kubectl.interface'; +import { IKubectlMetadata } from '../kubernetes/kubernetes.interface'; import { ISecurityContext } from "../settings/settings.interface" import { IAddon } from '../addons/addons.interface'; diff --git a/server/src/kubero.ts b/server/src/kubero.ts index c623ebcd..5455d807 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -899,6 +899,7 @@ export class Kubero { return this.config.podSizeList; } + //Migrated to settings public getConsoleEnabled(){ if (this.config.kubero?.console?.enabled == undefined) { return false; @@ -906,10 +907,12 @@ export class Kubero { return this.config.kubero?.console?.enabled; } + //Migrated to settings public setMetricsStatus(status: boolean) { this.features.metrics = status } + //Migrated to settings public getMetricsEnabled(): boolean{ return this.features.metrics } @@ -938,10 +941,12 @@ export class Kubero { }); } */ + //Migrated to settings public getBuildpipelineEnabled(){ return process.env.KUBERO_BUILD_REGISTRY ? process.env.KUBERO_BUILD_REGISTRY != undefined : false } + //Migrated to settings private async checkForZeropod(): Promise { // This is a very basic check for Zeropod. It requires the namespace zeropod-system to be present. // But it does not check if the Zeropod controller is complete and running. @@ -962,10 +967,12 @@ export class Kubero { return enabled } + //Migrated to settings public getSleepEnabled(): boolean { return this.features.sleep } + //migrated to settings public getAdminDisabled(){ if (this.config.kubero?.admin?.disabled == undefined) { return false; @@ -1468,6 +1475,7 @@ export class Kubero { return this.config.templates; } + //Migrated to settings public getTemplateEnabled() { return this.config.templates.enabled; } From f7b86938152aee4b06c79d14ef3292d6bb82a88a Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 7 Feb 2025 09:31:13 +0100 Subject: [PATCH 17/65] make kubernetes a global module --- server-refactored-v3/src/app.module.ts | 2 ++ server-refactored-v3/src/auth/auth.module.ts | 4 ++-- server-refactored-v3/src/auth/auth.service.ts | 4 ++-- server-refactored-v3/src/kubernetes/kubernetes.module.ts | 9 +++++++++ .../src/kubernetes/kubernetes.service.ts | 4 ++-- server-refactored-v3/src/settings/settings.module.ts | 4 ++-- server-refactored-v3/src/settings/settings.service.ts | 7 +++++-- 7 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 server-refactored-v3/src/kubernetes/kubernetes.module.ts diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 7d05caeb..578ac9ce 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -16,6 +16,7 @@ import { MetricsModule } from './metrics/metrics.module'; import { LogsModule } from './logs/logs.module'; import { DeploymentsModule } from './deployments/deployments.module'; import { CoreModule } from './core/core.module'; +import { KubernetesModule } from './kubernetes/kubernetes.module'; @Module({ @@ -36,6 +37,7 @@ import { CoreModule } from './core/core.module'; MetricsModule, LogsModule, DeploymentsModule, + KubernetesModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index a1516f8d..bc21e5fe 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; -import { Kubectl } from '../kubernetes/kubernetes.service'; import { UsersModule } from '../users/users.module'; +import { KubernetesModule } from 'src/kubernetes/kubernetes.module'; import { SettingsService } from '../settings/settings.service'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; @@ -9,7 +9,7 @@ import { AuthController } from './auth.controller'; @Module({ imports: [UsersModule, PassportModule], - providers: [AuthService, LocalStrategy, Kubectl, SettingsService], + providers: [AuthService, LocalStrategy, KubernetesModule, SettingsService], controllers: [AuthController], }) export class AuthModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/auth.service.ts b/server-refactored-v3/src/auth/auth.service.ts index 86832bc4..acabc382 100644 --- a/server-refactored-v3/src/auth/auth.service.ts +++ b/server-refactored-v3/src/auth/auth.service.ts @@ -1,13 +1,13 @@ import { Injectable, Request } from '@nestjs/common'; import { UsersService } from '../users/users.service'; -import { Kubectl } from '../kubernetes/kubernetes.service'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; import { SettingsService } from '../settings/settings.service'; @Injectable() export class AuthService { constructor( private usersService: UsersService, - private kubectl: Kubectl, + private kubectl: KubernetesService, private settingsService: SettingsService ) {} diff --git a/server-refactored-v3/src/kubernetes/kubernetes.module.ts b/server-refactored-v3/src/kubernetes/kubernetes.module.ts new file mode 100644 index 00000000..1310061c --- /dev/null +++ b/server-refactored-v3/src/kubernetes/kubernetes.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { KubernetesService } from './kubernetes.service'; + +@Global() +@Module({ + providers: [KubernetesService], + exports: [KubernetesService], +}) +export class KubernetesModule {} diff --git a/server-refactored-v3/src/kubernetes/kubernetes.service.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts index 92e59880..0159eb19 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.service.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -36,7 +36,7 @@ import stream from 'stream'; import internal from 'stream'; @Injectable() -export class Kubectl { +export class KubernetesService { private kc: KubeConfig; private versionApi: VersionApi = {} as VersionApi; private coreV1Api: CoreV1Api = {} as CoreV1Api; @@ -52,7 +52,7 @@ export class Kubectl { public log: KubeLog; //public config: IKuberoConfig; private exec: Exec = {} as Exec; - private readonly logger = new Logger(Kubectl.name); + private readonly logger = new Logger(KubernetesService.name); constructor() { this.kc = new KubeConfig(); diff --git a/server-refactored-v3/src/settings/settings.module.ts b/server-refactored-v3/src/settings/settings.module.ts index f0df2a6c..f735cce4 100644 --- a/server-refactored-v3/src/settings/settings.module.ts +++ b/server-refactored-v3/src/settings/settings.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { SettingsController } from './settings.controller'; import { SettingsService } from './settings.service'; -import { Kubectl } from '../kubernetes/kubernetes.service'; +import { KubernetesModule } from '../kubernetes/kubernetes.module'; @Module({ controllers: [SettingsController], - providers: [SettingsService, Kubectl] + providers: [SettingsService, KubernetesModule], }) export class SettingsModule {} diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 5b2812c0..33395e11 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { IKuberoConfig } from './settings.interface'; import { KuberoConfig } from './kubero-config/kubero-config'; -import { Kubectl } from '../kubernetes/kubernetes.service'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; import { readFileSync, writeFileSync } from 'fs'; import YAML from 'yaml' import { join } from 'path'; @@ -26,8 +26,11 @@ export class SettingsService { */ } - constructor(private readonly kubectl: Kubectl) { + constructor( + private readonly kubectl: KubernetesService, + ) { this.reloadRunningConfig() + this.runFeatureCheck() } // Load settings from a file or from kubernetes From c51876e66849852043156ac908e29cf91d0b5f2c Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 7 Feb 2025 04:15:31 +0100 Subject: [PATCH 18/65] add Addons and Pipeline --- client/src/layouts/default/AppBar.vue | 2 +- client/src/layouts/default/View.vue | 2 +- server-refactored-v3/.env.template | 6 + ...onfig.yaml.example => config.example.yaml} | 52 ++-- server-refactored-v3/package.json | 3 +- .../src/addons/addons.controller.spec.ts | 18 ++ .../src/addons/addons.controller.ts | 23 ++ .../src/addons/addons.module.ts | 9 + .../src/addons/addons.service.spec.ts | 18 ++ .../src/addons/addons.service.ts | 104 +++++++ .../src/addons/plugins/clickhouse.ts | 181 +++++++++++++ .../src/addons/plugins/cloudflare.ts | 129 +++++++++ .../src/addons/plugins/cockroachDB.ts | 114 ++++++++ .../src/addons/plugins/kuberoCouchDB.ts | 102 +++++++ .../src/addons/plugins/kuberoElasticsearch.ts | 102 +++++++ .../src/addons/plugins/kuberoKafka.ts | 54 ++++ .../src/addons/plugins/kuberoMail.ts | 62 +++++ .../src/addons/plugins/kuberoMemcached.ts | 119 ++++++++ .../src/addons/plugins/kuberoMongoDB.ts | 118 ++++++++ .../src/addons/plugins/kuberoMysql.ts | 94 +++++++ .../src/addons/plugins/kuberoPostgresql.ts | 86 ++++++ .../src/addons/plugins/kuberoRabbitMQ.ts | 94 +++++++ .../src/addons/plugins/kuberoRedis.ts | 78 ++++++ .../src/addons/plugins/minio.ts | 207 ++++++++++++++ .../src/addons/plugins/mongoDB.ts | 83 ++++++ .../src/addons/plugins/mongoDBCluster.ts | 54 ++++ .../src/addons/plugins/plugin.interface.ts | 25 ++ .../src/addons/plugins/plugin.ts | 179 ++++++++++++ .../src/addons/plugins/postgresCluster.ts | 190 +++++++++++++ .../src/addons/plugins/redis.ts | 81 ++++++ .../src/addons/plugins/redisCluster.ts | 86 ++++++ server-refactored-v3/src/app.module.ts | 4 + .../src/audit/audit.controller.spec.ts | 18 ++ .../src/audit/audit.controller.ts | 27 ++ .../src/audit/audit.interface.ts | 11 + .../src/audit/audit.module.ts | 10 + .../src/audit/audit.service.spec.ts | 18 ++ .../src/audit/audit.service.ts | 256 ++++++++++++++++++ server-refactored-v3/src/auth/auth.module.ts | 6 +- server-refactored-v3/src/auth/auth.service.ts | 8 +- .../src/kubernetes/kubernetes.service.ts | 6 +- server-refactored-v3/src/logger/logger.ts | 6 +- .../pipelines/pipelines.controller.spec.ts | 18 ++ .../src/pipelines/pipelines.controller.ts | 45 +++ .../src/pipelines/pipelines.interface.ts | 5 + .../src/pipelines/pipelines.module.ts | 7 +- .../src/pipelines/pipelines.service.spec.ts | 18 ++ .../src/pipelines/pipelines.service.ts | 22 ++ .../src/settings/settings.controller.ts | 5 + .../src/settings/settings.interface.ts | 36 +++ .../src/settings/settings.service.ts | 24 +- server-refactored-v3/yarn.lock | 19 ++ server/src/kubero.ts | 2 +- server/src/types.ts | 1 + 54 files changed, 2996 insertions(+), 51 deletions(-) rename server-refactored-v3/{config.yaml.example => config.example.yaml} (94%) create mode 100644 server-refactored-v3/src/addons/addons.controller.spec.ts create mode 100644 server-refactored-v3/src/addons/addons.controller.ts create mode 100644 server-refactored-v3/src/addons/addons.module.ts create mode 100644 server-refactored-v3/src/addons/addons.service.spec.ts create mode 100644 server-refactored-v3/src/addons/addons.service.ts create mode 100644 server-refactored-v3/src/addons/plugins/clickhouse.ts create mode 100644 server-refactored-v3/src/addons/plugins/cloudflare.ts create mode 100644 server-refactored-v3/src/addons/plugins/cockroachDB.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoKafka.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoMail.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoMemcached.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoMysql.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts create mode 100644 server-refactored-v3/src/addons/plugins/kuberoRedis.ts create mode 100644 server-refactored-v3/src/addons/plugins/minio.ts create mode 100644 server-refactored-v3/src/addons/plugins/mongoDB.ts create mode 100644 server-refactored-v3/src/addons/plugins/mongoDBCluster.ts create mode 100644 server-refactored-v3/src/addons/plugins/plugin.interface.ts create mode 100644 server-refactored-v3/src/addons/plugins/plugin.ts create mode 100644 server-refactored-v3/src/addons/plugins/postgresCluster.ts create mode 100644 server-refactored-v3/src/addons/plugins/redis.ts create mode 100644 server-refactored-v3/src/addons/plugins/redisCluster.ts create mode 100644 server-refactored-v3/src/audit/audit.controller.spec.ts create mode 100644 server-refactored-v3/src/audit/audit.controller.ts create mode 100644 server-refactored-v3/src/audit/audit.interface.ts create mode 100644 server-refactored-v3/src/audit/audit.module.ts create mode 100644 server-refactored-v3/src/audit/audit.service.spec.ts create mode 100644 server-refactored-v3/src/audit/audit.service.ts create mode 100644 server-refactored-v3/src/pipelines/pipelines.controller.spec.ts create mode 100644 server-refactored-v3/src/pipelines/pipelines.controller.ts create mode 100644 server-refactored-v3/src/pipelines/pipelines.service.spec.ts create mode 100644 server-refactored-v3/src/pipelines/pipelines.service.ts diff --git a/client/src/layouts/default/AppBar.vue b/client/src/layouts/default/AppBar.vue index 7c2626b0..d02d1ae3 100644 --- a/client/src/layouts/default/AppBar.vue +++ b/client/src/layouts/default/AppBar.vue @@ -25,7 +25,7 @@ export default defineComponent({ methods: { getBanner() { - axios.get('/api/banner').then((response: any) => { + axios.get('/api/settings/banner').then((response: any) => { this.banner.show = response.data.show; this.banner.message = response.data.message; this.banner.bgcolor = response.data.bgcolor; diff --git a/client/src/layouts/default/View.vue b/client/src/layouts/default/View.vue index b44f40cc..8fcb4aaf 100644 --- a/client/src/layouts/default/View.vue +++ b/client/src/layouts/default/View.vue @@ -35,7 +35,7 @@ export default defineComponent({ checkSession() { if (this.$route.name != 'Login') { axios - .get("/api/session") + .get("/api/auth/session") .then((result) => { console.log("isAuthenticated: " + result.data.isAuthenticated); diff --git a/server-refactored-v3/.env.template b/server-refactored-v3/.env.template index 1891cf50..039706e2 100644 --- a/server-refactored-v3/.env.template +++ b/server-refactored-v3/.env.template @@ -20,6 +20,12 @@ KUBERO_BUILD_REGISTRY=kubero-registry-yourdomain.com/something KUBERO_PROMETHEUS_ENDPOINT=http://prometheus.localhost +KUBERO_AUDIT=false +KUBERO_AUDIT_DB_PATH=./db +KUBERO_AUDIT_LIMIT=1000 + +#KUBERO_SETUP=enabled + ########################################## # git repository configuration # diff --git a/server-refactored-v3/config.yaml.example b/server-refactored-v3/config.example.yaml similarity index 94% rename from server-refactored-v3/config.yaml.example rename to server-refactored-v3/config.example.yaml index 777b76c9..6410920a 100644 --- a/server-refactored-v3/config.yaml.example +++ b/server-refactored-v3/config.example.yaml @@ -351,29 +351,29 @@ buildpacks: add: [] drop: [] podSizeList: -- name: small - description: 'Small (CPU: 0.25, Memory: 0.5Gi)' - default: true - resources: - requests: - memory: 0.5Gi - cpu: 250m - limits: - memory: 1Gi - cpu: 500m -- name: medium - description: 'Medium (CPU: 1, Memory: 2Gi)' - resources: - requests: - memory: 2Gi - cpu: 1000m - limits: - memory: 4Gi - cpu: 2000m -- name: large - description: 'Large (CPU: 2, Memory: 4Gi)' - active: false - resources: - requests: - memory: 4Gi - cpu: 2000m + - name: small + description: 'Small (CPU: 0.25, Memory: 0.5Gi)' + default: true + resources: + requests: + memory: 0.5Gi + cpu: 250m + limits: + memory: 1Gi + cpu: 500m + - name: medium + description: 'Medium (CPU: 1, Memory: 2Gi)' + resources: + requests: + memory: 2Gi + cpu: 1000m + limits: + memory: 4Gi + cpu: 2000m + - name: large + description: 'Large (CPU: 2, Memory: 4Gi)' + active: false + resources: + requests: + memory: 4Gi + cpu: 2000m diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index f5ccb305..89698db9 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -1,10 +1,10 @@ { - "name": "kubero", "description": "Heroku clone on Kubernetes", "main": "index.js", "author": "Gianni Carafa", "license": "GPL-3.0", + "version": "dev", "private": true, "scripts": { "build": "nest build", @@ -32,6 +32,7 @@ "@nestjs/swagger": "^11.0.3", "@nestjs/websockets": "^11.0.7", "@types/bcrypt": "^5.0.2", + "axios": "^1.7.9", "bcrypt": "^5.1.1", "dotenv": "^16.4.7", "passport": "^0.7.0", diff --git a/server-refactored-v3/src/addons/addons.controller.spec.ts b/server-refactored-v3/src/addons/addons.controller.spec.ts new file mode 100644 index 00000000..cbb2d93f --- /dev/null +++ b/server-refactored-v3/src/addons/addons.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AddonsController } from './addons.controller'; + +describe('AddonsController', () => { + let controller: AddonsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AddonsController], + }).compile(); + + controller = module.get(AddonsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/addons/addons.controller.ts b/server-refactored-v3/src/addons/addons.controller.ts new file mode 100644 index 00000000..caf792c1 --- /dev/null +++ b/server-refactored-v3/src/addons/addons.controller.ts @@ -0,0 +1,23 @@ +import { Controller, Get } from '@nestjs/common'; +import { AddonsService } from './addons.service'; +import { ApiOperation } from '@nestjs/swagger'; + +@Controller({ path: 'api/addons', version: '1' }) +export class AddonsController { + constructor( + private readonly addonsService: AddonsService + ) {} + + + @ApiOperation({ summary: 'Get a list of all addons' }) + @Get('/') + async getAddons() { + return this.addonsService.getAddonsList(); + } + + @ApiOperation({ summary: 'Get a list of all operators' }) + @Get('/operators') + async getOperators() { + return this.addonsService.getOperatorsList(); + } +} diff --git a/server-refactored-v3/src/addons/addons.module.ts b/server-refactored-v3/src/addons/addons.module.ts new file mode 100644 index 00000000..485aa530 --- /dev/null +++ b/server-refactored-v3/src/addons/addons.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { AddonsController } from './addons.controller'; +import { AddonsService } from './addons.service'; +@Module({ + controllers: [AddonsController], + providers: [AddonsService], +}) +export class AddonsModule { +} diff --git a/server-refactored-v3/src/addons/addons.service.spec.ts b/server-refactored-v3/src/addons/addons.service.spec.ts new file mode 100644 index 00000000..d8f29da9 --- /dev/null +++ b/server-refactored-v3/src/addons/addons.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AddonsService } from './addons.service'; + +describe('AddonsService', () => { + let service: AddonsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AddonsService], + }).compile(); + + service = module.get(AddonsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/addons/addons.service.ts b/server-refactored-v3/src/addons/addons.service.ts new file mode 100644 index 00000000..ca74c4f9 --- /dev/null +++ b/server-refactored-v3/src/addons/addons.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; +import { IPlugin } from './plugins/plugin.interface'; +import { KuberoMysql } from './plugins/kuberoMysql'; +import { KuberoRedis } from './plugins/kuberoRedis'; +import { KuberoPostgresql } from './plugins/kuberoPostgresql'; +import { KuberoMongoDB } from './plugins/kuberoMongoDB'; +import { KuberoMemcached } from './plugins/kuberoMemcached'; +import { KuberoElasticsearch } from './plugins/kuberoElasticsearch'; +import { KuberoCouchDB } from './plugins/kuberoCouchDB'; +import { KuberoKafka } from './plugins/kuberoKafka'; +import { KuberoMail } from './plugins/kuberoMail'; +import { KuberoRabbitMQ } from './plugins/kuberoRabbitMQ'; +import { Tunnel } from './plugins/cloudflare'; +import { PostgresCluster } from './plugins/postgresCluster'; +import { RedisCluster } from './plugins/redisCluster'; +import { Redis } from './plugins/redis'; +import { MongoDB } from './plugins/mongoDB'; +import { Cockroachdb } from './plugins/cockroachDB'; +import { Tenant } from './plugins/minio'; +import { ClickHouseInstallation } from './plugins/clickhouse'; + +import { KubernetesService } from '../kubernetes/kubernetes.service'; + +@Injectable() +export class AddonsService { + private operatorsAvailable: string[] = []; + public addonsList: IPlugin[] = [] // List or possibly installed operators + private CRDList: any; //List of installed CRDs from kubectl + + constructor(private kubectl: KubernetesService) { + this.loadOperators() + } + + public async loadOperators(): Promise { + + // Load all Custom Resource Definitions to get the list of installed operators + this.CRDList = await this.kubectl.getCustomresources() + + const kuberoMysql = new KuberoMysql(this.CRDList) + this.addonsList.push(kuberoMysql) + + const kuberoRedis = new KuberoRedis(this.CRDList) + this.addonsList.push(kuberoRedis) + + const kuberoPostgresql = new KuberoPostgresql(this.CRDList) + this.addonsList.push(kuberoPostgresql) + + const kuberoMongoDB = new KuberoMongoDB(this.CRDList) + this.addonsList.push(kuberoMongoDB) + + const kuberoMemcached = new KuberoMemcached(this.CRDList) + this.addonsList.push(kuberoMemcached) + + const kuberoElasticsearch = new KuberoElasticsearch(this.CRDList) + this.addonsList.push(kuberoElasticsearch) + + const kuberoCouchDB = new KuberoCouchDB(this.CRDList) + this.addonsList.push(kuberoCouchDB) + + const kuberoKafka = new KuberoKafka(this.CRDList) + this.addonsList.push(kuberoKafka) + + const kuberoMail = new KuberoMail(this.CRDList) + this.addonsList.push(kuberoMail) + + const kuberoRabbitMQ = new KuberoRabbitMQ(this.CRDList) + this.addonsList.push(kuberoRabbitMQ) + + const tunnel = new Tunnel(this.CRDList) + this.addonsList.push(tunnel) + + const postgresCluster = new PostgresCluster(this.CRDList) + this.addonsList.push(postgresCluster) + + const redisCluster = new RedisCluster(this.CRDList) + this.addonsList.push(redisCluster) + + const redis = new Redis(this.CRDList) + this.addonsList.push(redis) + + const mongoDB = new MongoDB(this.CRDList) + this.addonsList.push(mongoDB) + + const cockroachdb = new Cockroachdb(this.CRDList) + this.addonsList.push(cockroachdb) + + const minio = new Tenant(this.CRDList) + this.addonsList.push(minio) + + const clickhouse = new ClickHouseInstallation(this.CRDList) + this.addonsList.push(clickhouse) + + } + + public async getAddonsList(): Promise { + return this.addonsList + } + + public getOperatorsList(): string[] { + return this.operatorsAvailable + } + + +} diff --git a/server-refactored-v3/src/addons/plugins/clickhouse.ts b/server-refactored-v3/src/addons/plugins/clickhouse.ts new file mode 100644 index 00000000..f12be36e --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/clickhouse.ts @@ -0,0 +1,181 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class ClickHouseInstallation extends Plugin implements IPlugin { + public id: string = 'clickhouse-operator';//same as operator name + public displayName = 'ClickHouse Cluster' + public icon = '/img/addons/clickhouse.svg' + public install = 'curl -s https://raw.githubusercontent.com/Altinity/clickhouse-operator/master/deploy/operator-web-installer/clickhouse-operator-install.sh | OPERATOR_NAMESPACE=clickhouse-operator-system bash' + public url = 'https://github.com/Altinity/clickhouse-operator/' + public description: string = 'ClickHouse is an open source column-oriented database management system capable of real time generation of analytical data reports. Check ClickHouse documentation for more complete details.' + public links = [ + { + name: 'Altinity', url: 'https://altinity.com/', + }, + { + name: 'Operator homepage', url: 'https://www.altinity.com/kubernetes-operator' + }, + { + name: 'Documentation', url: 'https://github.com/Altinity/clickhouse-operator/tree/master/docs' + } + ] + public maintainers = [ + { + name: 'Altinity', + email: 'support@altinity.com', + url: 'https://altinity.com', + github: 'altinity' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/clickhouse' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'ClickHouseInstallation.metadata.name':{ + type: 'text', + label: 'Name *', + name: 'metadata.name', + required: true, + default: 'clickhouse', + description: 'The name of the Clickhouse instance' + }, + 'ClickHouseInstallation.spec.configuration.clusters[0].layout.shardsCount':{ + type: 'number', + label: 'Shards Count *', + name: 'spec.configuration.clusters[0].layout.shardsCount', + default: 1, + required: true, + description: 'Number of shards' + }, + 'ClickHouseInstallation.spec.configuration.clusters[0].layout.replicasCount':{ + type: 'number', + label: 'Replicas Count *', + name: 'spec.configuration.clusters[0].layout.replicasCount', + default: 1, + required: true, + description: 'Number of replicas' + }, + "ClickHouseInstallation.spec.configuration.users['admin/password']":{ + type: 'text', + label: 'Admin Password *', + name: "ClickHouseInstallation.spec.configuration.users['admin/password']", + default: 'ChangeMe', + required: true, + description: 'Password for user "user"' + }, + "ClickHouseInstallation.spec.configuration.users['admin/networks/ip'][0]":{ + type: 'text', + label: 'Admin Access Network *', + name: "ClickHouseInstallation.spec.configuration.users['admin/networks/ip'][0]", + default: '0.0.0.0/0', + required: true, + description: 'Allowed Network access for "admin"' + }, + "ClickHouseInstallation.spec.configuration.users['user/password']":{ + type: 'text', + label: 'User Password *', + name: "ClickHouseInstallation.spec.configuration.users['user/password']", + default: 'ChangeMe', + required: true, + description: 'Password for user "user"' + }, + "ClickHouseInstallation.spec.configuration.users['user/networks/ip'][0]":{ + type: 'text', + label: 'User Access Network *', + name: "ClickHouseInstallation.spec.configuration.users['user/networks/ip'][0]", + default: '0.0.0.0/0', + required: true, + description: 'Allowed Network access for "user"' + }, + 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage':{ + type: 'text', + label: 'Data Storage Size*', + name: 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage', + default: '1Gi', + required: true, + description: 'Size of the data storage' + }, + 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[1].spec.resources.requests.storage':{ + type: 'text', + label: 'Log Storage Size*', + name: 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage', + default: '1Gi', + required: true, + description: 'Size of the log storage' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + + public resourceDefinitions: any = { + ClickHouseInstallation: { + apiVersion: "clickhouse.altinity.com/v1", + kind: "ClickHouseInstallation", + metadata: { + name: "example" + }, + spec: { + configuration: { + users: { + 'user/password': "user_password", + 'user/networks/ip': [ + "0.0.0.0/0" + ], + 'admin/password': "admin_password", + 'admin/networks/ip': [ + "0.0.0.0/0" + ] + }, + clusters: [ + { + name: "example", + layout: { + shardsCount: 1, + replicasCount: 2 + } + } + ] + }, + templates: { + volumeClaimTemplates: [ + { + name: "data-volume-template", + spec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "1Gi" + } + } + } + }, + { + name: "log-volume-template", + spec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "100Mi" + } + } + } + } + ] + } + } + } + } + +} + diff --git a/server-refactored-v3/src/addons/plugins/cloudflare.ts b/server-refactored-v3/src/addons/plugins/cloudflare.ts new file mode 100644 index 00000000..bf044616 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/cloudflare.ts @@ -0,0 +1,129 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class Tunnel extends Plugin implements IPlugin { + public id: string = 'cloudflare-operator';//same as operator name + public displayName = 'Cloudflare Tunnel' + public icon = '/img/addons/cloudflare.svg' + public install: string = 'kubectl apply -k https://github.com/adyanth/cloudflare-operator/config/default' + public url = 'https://github.com/adyanth/cloudflare-operator' + public description: string = 'Cloudflared establishes outbound connections (tunnels) between your resources and Cloudflare\'s global network. Tunnels are persistent objects that route traffic to DNS records. Within the same tunnel, you can run as many cloudflared processes (connectors) as needed.' + public links = [ + { + name: 'Getting started', url: 'https://github.com/adyanth/cloudflare-operator/blob/main/docs/getting-started.md', + }, + { + name: 'Cloudflare Tunnel', url: 'https://developers.cloudflare.com/cloudflare-one/connections/connect-apps' + }, + { + name: 'Blog Post', url: 'https://adyanth.site/posts/migration-compose-k8s/cloudflare-tunnel-operator-architecture/' + } + ] + public maintainers = [ + { + name: 'Adyanth Hosavalike', + email: 'me@adyanth.dev', + url: 'https://adyanth.site', + github: 'adyanth' + } + ] + public artifact_url = 'https://www.httpbin.org/status/404' // Not available on ArtifactHub + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'Tunnel.metadata.name':{ + type: 'text', + label: 'Name', + name: 'metadata.name', + required: true, + default: 'cloudflare-tunnel', + description: 'The name of the Cloudflare Tunnel' + }, + 'Tunnel.spec.cloudflare.domain':{ + type: 'text', + label: 'Domain*', + name: 'spec.memcached.domain', + default: '', + required: true, + description: 'Memcached admin user' + }, + 'Tunnel.spec.cloudflare.email':{ + type: 'text', + label: 'E-mail*', + name: 'spec.cloudflare.email', + default: '', + required: true, + description: 'Email address associated with the Cloudflare account' + }, + 'Tunnel.spec.cloudflare.accountName':{ + type: 'text', + label: 'Account Name*', + name: 'spec.cloudflare.accountName', + default: '', + required: true, + description: 'Cloudflare Account Name' + }, + /* Fallback to Account Name + 'Tunnel.spec.cloudflare.accountId':{ + type: 'text', + label: 'Account ID', + name: 'spec.cloudflare.accountId', + default: '', + required: false, + description: 'Cloudflare Account ID' + }, + */ + 'Tunnel.spec.cloudflare.CLOUDFLARE_API_TOKEN':{ + type: 'text', + label: 'API Token*', + name: 'spec.cloudflare.CLOUDFLARE_API_TOKEN', + default: '', + required: true, + description: 'Cloudflare API Token' + }, + 'Tunnel.spec.cloudflare.CLOUDFLARE_API_KEY':{ + type: 'text', + label: 'API Key*', + name: 'spec.cloudflare.CLOUDFLARE_API_KEY', + default: '', + required: true, + description: 'Cloudflare API Key' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + public resourceDefinitions: any = { + Tunnel: { + apiVersion: "networking.cfargotunnel.com/v1alpha1", + kind: "Tunnel", + metadata: { + name: "new-tunnel" + }, + spec: { + newTunnel: { + name: "new-k8s-tunnel" + }, + size: 2, + cloudflare: { + domain: "example.com", + secret: "cloudflare-secrets", + email: "email@domain.com", + accountName: "", + //accountId: "", + CLOUDFLARE_API_TOKEN: "", + CLOUDFLARE_API_KEY: "" + } + } + } + } + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} + diff --git a/server-refactored-v3/src/addons/plugins/cockroachDB.ts b/server-refactored-v3/src/addons/plugins/cockroachDB.ts new file mode 100644 index 00000000..8b58c207 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/cockroachDB.ts @@ -0,0 +1,114 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class Cockroachdb extends Plugin implements IPlugin { + public id: string = 'cockroachdb';//same as operator name + public displayName = 'CockroachDB' + public icon = '/img/addons/CockroachDB.svg' + public install: string = 'kubectl create -f https://operatorhub.io/install/cockroachdb.yaml' + public install_olm: string = 'kubectl create -f https://operatorhub.io/install/cockroachdb.yaml' + public url = 'https://artifacthub.io/packages/olm/community-operators/cockroachdb' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'Cockroachdb.metadata.name':{ + type: 'text', + label: 'MongoDB Name', + name: 'MongoDB.metadata.name', + required: true, + default: 'mongodbinstance', + description: 'The name of the MongoDB cluster' + }, + 'Cockroachdb.conf.cache':{ + type: 'text', + label: 'Cache Size', + name: 'Cockroachdb.conf.cache', + required: true, + default: '25%', + description: 'Size of the cache' + }, + 'Cockroachdb.conf.max-sql-memory':{ + type: 'text', + label: 'Max SQL Memory', + name: 'Cockroachdb.conf.max-sql-memory', + required: true, + default: '25%', + description: 'Max SQL Memory' + }, + 'Cockroachdb.conf.single-node':{ + type: 'switch', + label: 'Single Node', + name: 'Cockroachdb.conf.single-node', + required: false, + default: false, + description: 'Single Node' + }, + 'Cockroachdb.statefulset.replicas':{ + type: 'number', + label: 'Replicas', + name: 'Cockroachdb.statefulset.replicas', + required: true, + default: 3, + description: 'Number of Replicas' + }, + 'Cockroachdb.spec.storage.persistentVolume.storageSize':{ + type: 'text', + label: 'Sorage Size', + name: 'MongoDB.spec.storage.storageSize', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + 'Cockroachdb.spec.storage.persistentVolume.storageClass':{ + type: 'select-storageclass', + label: 'Sorage Class', + name: 'MongoDB.spec.storage.storageClass', + default: 'standard', + required: true, + description: 'Classname of the storage' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + + public resourceDefinitions: any = { + 'Cockroachdb': { + apiVersion: "charts.operatorhub.io/v1alpha1", + kind: "Cockroachdb", + metadata: { + name: "cockroachdbinstance", + }, + spec: { + cache: "25%", + 'max-sql-memory': "25%", + 'single-node': false, + statefulset: { + replicas: 3 + }, + storage: { + persistentVolume: { + storageSize: "1Gi", + storageClass: "standard" + } + } + } + } + } + +} + + + diff --git a/server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts b/server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts new file mode 100644 index 00000000..c67790f9 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts @@ -0,0 +1,102 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoCouchDB extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'CouchDB' + public icon = '/img/addons/couchdb.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoCouchDB.metadata.name':{ + type: 'text', + label: 'Couchdb DB Name', + name: 'metadata.name', + required: true, + default: 'couchdb', + description: 'The name of the Couchdb instance' + }, + 'KuberoCouchDB.spec.couchdb.clusterSize':{ + type: 'number', + label: 'Cluster Size*', + name: 'spec.couchdb.clusterSize', + default: 3, + required: true, + description: 'Number of replicas' + }, + 'KuberoCouchDB.spec.couchdb.persistentVolume.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.couchdb.persistentVolume.storageClass', + default: 'default', + required: true + }, + 'KuberoCouchDB.spec.couchdb.persistentVolume.size':{ + type: 'text', + label: 'Storage Size*', + name: 'spec.couchdb.persistentVolume.size', + default: '8Gi', + required: true, + description: 'Size of the storage' + }, + 'KuberoCouchDB.spec.couchdb.adminUsername':{ + type: 'text', + label: 'Admin Username*', + name: 'spec.couchdb.adminUsername', + default: 'admin', + required: true, + description: 'Admin Username' + }, + 'KuberoCouchDB.spec.couchdb.adminPassword':{ + type: 'text', + label: 'Admin Password*', + name: 'spec.couchdb.auth.rootPassword', + default: '', + required: true, + description: 'Admin Password' + }, + 'KuberoCouchDB.spec.couchdb.adminHash':{ + type: 'text', + label: 'Admin Hash*', + name: 'spec.couchdb.adminHash', + default: '', + required: true, + description: 'Random character string' + }, + 'KuberoCouchDB.spec.couchdb.cookieAuthSecret':{ + type: 'text', + label: 'Cookie Auth Secret*', + name: 'spec.couchdb.cookieAuthSecret', + default: '', + required: true, + description: 'Random character string' + }, + 'KuberoCouchDB.spec.couchdb.couchdbConfig.couchdb.uuid':{ + type: 'text', + label: 'instance UUID*', + name: 'spec.couchdb.couchdbConfig.couchdb.uuid', + default: '', + required: true, + description: 'Random character string' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts b/server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts new file mode 100644 index 00000000..2a88c944 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts @@ -0,0 +1,102 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoElasticsearch extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'Elasticsearch' + public icon = '/img/addons/elasticsearch.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoElasticsearch.metadata.name':{ + type: 'text', + label: 'Elasticsearch Index Name', + name: 'metadata.name', + required: true, + default: 'elasticsearch', + description: 'The name of the elasticsearch instance' + }, + 'KuberoElasticsearch.spec.elasticsearch.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.elasticsearch.global.storageClass', + default: 'default', + required: true + }, + 'KuberoElasticsearch.spec.elasticsearch.security.elasticPassword':{ + type: 'text', + label: 'User elastic Password*', + name: 'spec.elasticsearch.security.elasticPassword', + default: '', + required: true, + description: 'Password for the user elastic' + }, + 'KuberoElasticsearch.spec.elasticsearch.master.persistence.size':{ + type: 'text', + label: 'Master Storage Size*', + name: 'spec.elasticsearch.master.persistence.size', + default: '8Gi', + required: true, + description: 'Size of the Master storage' + }, + 'KuberoElasticsearch.spec.elasticsearch.master.replicaCount':{ + type: 'number', + label: 'Master Replica Count*', + name: 'spec.elasticsearch.master.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of Master Elasticsearch nodes' + }, + 'KuberoElasticsearch.spec.elasticsearch.data.persistence.size':{ + type: 'text', + label: 'Data Storage Size*', + name: 'spec.spec.elasticsearch.data.persistence.size', + default: '8Gi', + required: true, + description: 'Size of the Data storage' + }, + 'KuberoElasticsearch.spec.elasticsearch.data.replicaCount':{ + type: 'number', + label: 'Data Replica Count*', + name: 'spec.elasticsearch.data.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of Data Elasticsearch nodes' + }, + 'KuberoElasticsearch.spec.elasticsearch.ingest.enabled':{ + type: 'switch', + label: 'Ingest enabled*', + name: 'spec.elasticsearch.ingest.enabled', + default: true, + required: false, + description: 'Ingest enabled' + }, + 'KuberoElasticsearch.spec.elasticsearch.ingest.replicaCount':{ + type: 'number', + label: 'Ingest Replica Count*', + name: 'spec.elasticsearch.ingest.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of Data Elasticsearch nodes' + } + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoKafka.ts b/server-refactored-v3/src/addons/plugins/kuberoKafka.ts new file mode 100644 index 00000000..591419ed --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoKafka.ts @@ -0,0 +1,54 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoKafka extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'Kafka' + public icon = '/img/addons/kafka.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoKafka.metadata.name':{ + type: 'text', + label: 'Kafka DB Name', + name: 'metadata.name', + required: true, + default: 'kafka', + description: 'The name of the Kafka instance' + }, + 'KuberoKafka.spec.kafka.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.kafka.global.storageClass', + default: 'default', + required: true + }, + 'KuberoKafka.spec.kafka.persistence.size':{ + type: 'text', + label: 'Storage Size*', + name: 'spec.kafka.persistence.size', + default: '8Gi', + required: true, + description: 'Size of the storage' + } + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoMail.ts b/server-refactored-v3/src/addons/plugins/kuberoMail.ts new file mode 100644 index 00000000..1716a104 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoMail.ts @@ -0,0 +1,62 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoMail extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'Haraka Mail Server' + public icon = '/img/addons/Haraka.png' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoMail.metadata.name':{ + type: 'text', + label: 'Mail Server Name', + name: 'metadata.name', + required: true, + default: 'haraka', + description: 'The name of the mail server instance' + }, + 'KuberoMail.spec.haraka.haraka.env[0].value':{ + type: 'text', + label: 'Hostlist*', + name: 'KuberoMail.spec.haraka.haraka.env[0].value', + default: 'localhost,localhost.kubero.dev', + required: true, + description: 'A comma separated list of hostnames for which the mail server should accept mail' + }, + 'KuberoMail.spec.haraka.haraka.env[1].value':{ + type: 'text', + label: 'Server name*', + name: 'KuberoMail.spec.haraka.haraka.env[1].value', + default: 'info', + required: true, + description: 'Single string for the server name: me' + }, + 'KuberoMail.spec.haraka.haraka.env[6].value':{ + type: 'text', + label: 'Log Level*', + name: 'KuberoMail.spec.haraka.haraka.env[6].value', + default: 'info', + required: true, + description: 'HaraKa log level: info, warn, error, debug' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoMemcached.ts b/server-refactored-v3/src/addons/plugins/kuberoMemcached.ts new file mode 100644 index 00000000..baef0647 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoMemcached.ts @@ -0,0 +1,119 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoMemcached extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'Memcached' + public icon = '/img/addons/memcached.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoMemcached.metadata.name':{ + type: 'text', + label: 'Name', + name: 'metadata.name', + required: true, + default: 'memcached', + description: 'The name of the Memcached instance' + }, + 'KuberoMemcached.spec.memcached.architecture':{ + type: 'select', + label: 'Architecture*', + options: ['standalone', 'high-availability'], + name: 'spec.memcached.architecture', + default: 'standalone', + required: true, + description: 'Architecture of the Memcached instance' + }, + 'KuberoMemcached.spec.memcached.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.memcached.global.storageClass', + default: 'default', + required: true + }, + 'KuberoMemcached.spec.memcached.auth.enabled':{ + type: 'switch', + label: 'Enable Authentication', + name: 'spec.memcached.auth.username', + default: true, + required: false, + description: 'Enable Memcached authentication' + }, + 'KuberoMemcached.spec.memcached.auth.username':{ + type: 'text', + label: 'Username', + name: 'spec.memcached.auth.username', + default: '', + required: false, + description: 'Memcached admin user' + }, + 'KuberoMemcached.spec.memcached.auth.password':{ + type: 'text', + label: 'Password', + name: 'spec.memcached.auth.password', + default: '', + required: false, + description: 'Memcached admin password' + }, + 'KuberoMemcached.spec.memcached.resources.requests.memory':{ + type: 'text', + label: 'Memory', + name: 'spec.memcached.resources.requests.memory', + default: '256Mi', + required: true, + description: 'Memcached memory reservation' + }, + 'KuberoMemcached.spec.memcached.replicaCount':{ + type: 'number', + label: 'Replica Count', + name: 'spec.memcached.replicaCount', + default: 1, + required: true, + description: 'Number of Memcached replicas' + }, + 'KuberoMemcached.spec.memcached.autoscaling.enabled':{ + type: 'switch', + label: 'Enable Autoscaling', + name: 'spec.memcached.autoscaling.enabled', + default: true, + required: false, + description: 'Requires Architecture "high-avialable"' + }, + 'KuberoMemcached.spec.memcached.autoscaling.minReplicas':{ + type: 'number', + label: 'Min Replica Count', + name: 'spec.memcached.autoscaling.minReplicas', + default: 3, + required: false, + description: 'Minimal number of Memcached replicas' + }, + 'KuberoMemcached.spec.memcached.autoscaling.maxReplicas':{ + type: 'number', + label: 'Max Replica Count', + name: 'spec.memcached.autoscaling.maxReplicas', + default: 6, + required: false, + description: 'Maximal number of Memcached replicas' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts b/server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts new file mode 100644 index 00000000..7e050050 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts @@ -0,0 +1,118 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoMongoDB extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'MongoDB' + public icon = '/img/addons/mongo.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoMongoDB.metadata.name':{ + type: 'text', + label: 'MongoDB Name', + name: 'metadata.name', + required: true, + default: 'mongodb', + description: 'The name of tht MongoDB instance' + }, + 'KuberoMongoDB.spec.mongodb.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.mongodb.global.storageClass', + default: 'default', + required: true + }, + 'KuberoMongoDB.spec.mongodb.persistence.size':{ + type: 'text', + label: 'Sorage Size*', + name: 'spec.mongodb.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + 'KuberoMongoDB.spec.mongodb.architecture':{ + type: 'select', + label: 'Architecture*', + options: ['standalone', 'replicaset'], + name: 'spec.mongodb.architecture', + default: 'standalone', + required: true + }, + 'KuberoMongoDB.spec.mongodb.auth.databases[0]':{ + type: 'text', + label: 'Database*', + name: 'spec.mongodb.auth.databases[0]', + default: '', + required: true, + description: 'Database Name' + }, + 'KuberoMongoDB.spec.mongodb.auth.rootPassword':{ + type: 'text', + label: 'Root Password*', + name: 'spec.mongodb.auth.rootPassword', + default: '', + required: true, + description: 'Root Password' + }, + 'KuberoMongoDB.spec.mongodb.auth.usernames[0]':{ + type: 'text', + label: 'Username*', + name: 'spec.mongodb.auth.usernames[0]', + default: '', + required: true, + description: 'Additional username' + }, + 'KuberoMongoDB.spec.mongodb.auth.passwords[0]':{ + type: 'text', + label: 'User Password*', + name: 'spec.mongodb.auth.passwords[0]', + default: '', + required: true, + description: 'Password for the additional user' + }, + 'KuberoMongoDB.spec.mongodb.directoryPerDB':{ + type: 'switch', + label: 'Directory per DB', + name: 'spec.mongodb.directoryPerDB', + default: false, + required: false, + description: 'Directory per DB' + }, + 'KuberoMongoDB.spec.mongodb.disableJavascript':{ + type: 'switch', + label: 'Disable Javascript', + name: 'spec.mongodb.disableJavascript', + default: false, + required: false, + description: 'Disable Javascript' + }, + 'KuberoMongoDB.spec.mongodb.replicaCount':{ + type: 'number', + label: 'Replica Count*', + name: 'spec.mongodb.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of MongoDB nodes' + } + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoMysql.ts b/server-refactored-v3/src/addons/plugins/kuberoMysql.ts new file mode 100644 index 00000000..1bb03372 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoMysql.ts @@ -0,0 +1,94 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoMysql extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'MySQL' + public icon = '/img/addons/mysql.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoMysql.metadata.name':{ + type: 'text', + label: 'MySQL DB Name', + name: 'metadata.name', + required: true, + default: 'mysql', + description: 'The name of the MySQL instance' + }, + 'KuberoMysql.spec.mysql.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.mysql.global.storageClass', + default: 'standard', + required: true + }, + 'KuberoMysql.spec.mysql.primary.persistence.size':{ + type: 'text', + label: 'Sorage Size*', + name: 'spec.mysql.primary.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + 'KuberoMysql.spec.mysql.auth.createDatabase':{ + type: 'switch', + label: 'Create a Database*', + name: 'spec.mysql.auth.createDatabase', + default: false, + required: false, + description: 'Create a database on MySQL startup' + }, + 'KuberoMysql.spec.mysql.auth.database':{ + type: 'text', + label: 'Database Name*', + name: 'spec.mysql.auth.database', + default: '', + required: true, + description: 'Name of the database to create' + }, + 'KuberoMysql.spec.mysql.auth.rootPassword':{ + type: 'text', + label: 'Root Password*', + name: 'spec.mysql.auth.rootPassword', + default: '', + required: true, + description: 'Root Password' + }, + 'KuberoMysql.spec.mysql.auth.username':{ + type: 'text', + label: 'Username*', + name: 'spec.mysql.auth.username', + default: '', + required: true, + description: 'Additional username' + }, + 'KuberoMysql.spec.mysql.auth.password':{ + type: 'text', + label: 'User Password*', + name: 'spec.mysql.auth.password', + default: '', + required: true, + description: 'Password for the additional user' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts b/server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts new file mode 100644 index 00000000..135a462a --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts @@ -0,0 +1,86 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoPostgresql extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'Postgresql' + public icon = '/img/addons/pgsql.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoPostgresql.metadata.name':{ + type: 'text', + label: 'PostgreSQL Instance Name', + name: 'metadata.name', + required: true, + default: 'postgresql', + description: 'The name of the PostgreSQL instance' + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.postgresPassword':{ + type: 'text', + label: 'Postgres admin Password*', + name: 'spec.postgresql.global.postgresql.auth.postgresPassword', + default: '', + required: true, + description: 'Password for the "postgres" admin user' + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.username':{ + type: 'text', + label: 'Username*', + name: 'spec.postgresql.global.postgresql.auth.username', + default: '', + required: true, + description: 'Username for an additional user to create' + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.password':{ + type: 'text', + label: 'User Password*', + name: 'spec.postgresql.global.postgresql.auth.password', + default: '', + required: true, + description: 'Password for an additional user to create' + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.database':{ + type: 'text', + label: 'Database*', + name: 'spec.postgresql.global.postgresql.auth.database', + default: 'postgresql', + required: true, + description: 'Name for a custom database to create' + }, + 'KuberoPostgresql.spec.postgresql.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.postgresql.global.storageClass', + default: 'default', + required: true + }, + 'KuberoPostgresql.spec.postgresql.primary.persistence.size':{ + type: 'text', + label: 'Sorage Size*', + name: 'spec.postgresql.primary.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts b/server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts new file mode 100644 index 00000000..532c81d2 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts @@ -0,0 +1,94 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoRabbitMQ extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'RabbitMQ' + public icon = '/img/addons/RabbitMQ.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoRabbitMQ.metadata.name':{ + type: 'text', + label: 'RabbitMQ Instance Name', + name: 'metadata.name', + required: true, + default: 'rabbitmq', + description: 'The name of the PostgreSQL instance' + }, + 'KuberoRabbitMQ.spec.rabbitmq.auth.username':{ + type: 'text', + label: 'User Name*', + name: 'spec.rabbitmq.auth.username', + default: '', + required: true, + description: 'Username' + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.auth.password':{ + type: 'text', + label: 'User Password', + name: 'spec.rabbitmq.auth.password', + default: '', + required: true, + description: 'Password' + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.rabbitmq.auth.securepassword':{ + type: 'text', + label: 'Secure Password', + name: 'spec.rabbitmq.global.rabbitmq.auth.securepassword', + default: '', + required: false, + description: 'Secure Password' + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.rabbitmq.auth.erlangCookie':{ + type: 'text', + label: 'Erlang Cookie', + name: 'spec.rabbitmq.global.rabbitmq.auth.erlangCookie', + default: '', + required: false, + description: 'Erlang Cookie' + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.rabbitmq.global.storageClass', + default: 'default', + required: true + }, + 'KuberoRabbitMQ.spec.rabbitmq.maxAvailableSchedulers':{ + type: 'number', + label: 'Max Available Schedulers', + name: 'spec.rabbitmq.maxAvailableSchedulers', + default: '', + required: false, + description: 'Max available schedulers' + }, + 'KuberoRabbitMQ.spec.rabbitmq.onlineSchedulers':{ + type: 'number', + label: 'Online Schedulers', + name: 'spec.rabbitmq.onlineSchedulers', + default: '', + required: false, + description: 'Online schedulers' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/kuberoRedis.ts b/server-refactored-v3/src/addons/plugins/kuberoRedis.ts new file mode 100644 index 00000000..ab652492 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/kuberoRedis.ts @@ -0,0 +1,78 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class KuberoRedis extends Plugin implements IPlugin { + public id: string = 'kubero-operator';//same as operator name + public displayName = 'Redis' + public icon = '/img/addons/redis.svg' + public install: string = '' + public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' + public beta: boolean = false; + + public formfields: {[key: string]: IPluginFormFields} = { + 'KuberoRedis.metadata.name':{ + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'redis', + description: 'The name of the redis instance' + }, + 'KuberoRedis.spec.redis.replica.replicaCount':{ + type: 'number', + label: 'Replica Count*', + name: 'spec.redis.replica.replicaCount', + default: '3', + required: true, + description: 'Number of replicas' + }, + 'KuberoRedis.spec.redis.global.storageClass':{ + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.redis.global.storageClass', + default: 'default', + required: true + }, + 'KuberoRedis.spec.redis.master.persistence.size':{ + type: 'text', + label: 'Master Sorage Size*', + name: 'spec.redis.master.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + 'KuberoRedis.spec.redis.replica.persistence.size':{ + type: 'text', + label: 'Replica Sorage Size*', + name: 'spec.redis.replica.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + 'KuberoRedis.spec.redis.global.redis.password':{ + type: 'text', + label: 'Password*', + name: 'spec.redis.global.redis.password', + default: '', + required: true, + description: 'Password' + } + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/minio.ts b/server-refactored-v3/src/addons/plugins/minio.ts new file mode 100644 index 00000000..acbad164 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/minio.ts @@ -0,0 +1,207 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class Tenant extends Plugin implements IPlugin { + public id: string = 'minio-operator';//same as operator name + public displayName = 'Minio' + public icon = '/img/addons/Minio.png' + public install: string = 'kubectl create -f https://operatorhub.io/install/stable/minio-operator.yaml -n operators' + public url = 'https://artifacthub.io/packages/olm/community-operators/minio-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/minio-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'Tenant.metadata.name':{ + type: 'text', + label: 'Minio Cluster Name', + name: 'metadata.name', + required: true, + default: 'storage-lite', + description: 'The name of the Minio cluster' + }, + 'Tenant.spec.pools[0].servers':{ + type: 'number', + label: 'Clustersize', + name: 'Tenant.spec.pools[0].servers', + default: 4, + required: true, + description: 'Number of pool servers' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = { + // TODO requires to deploy some secrets + /* + E1019 13:11:37.950072 1 main-controller.go:584] error syncing 'another-production/storage-lite': secrets "storage-configuration" not found + 2022/10/19 13:11:38 http: TLS handshake error from 10.244.0.1:57646: remote error: tls: bad certificate + */ + + Tenant: { + apiVersion: "minio.min.io/v2", + kind: "Tenant", + metadata: { + annotations: { + 'prometheus.io/path': "/minio/v2/metrics/cluster", + 'prometheus.io/port': "9000", + 'prometheus.io/scrape': "true" + }, + labels: { + app: "minio" + }, + name: "storage-lite", + }, + spec: { + certConfig: {}, + configuration: { + name: "storage-configuration" + }, + env: [], + externalCaCertSecret: [], + externalCertSecret: [], + externalClientCertSecrets: [], + features: { + bucketDNS: false, + domains: {} + }, + image: "quay.io/minio/minio@sha256:c3d20bc2ea08477248c15f932822f932b092b658c5692a9c9f4d88abcf556ed7", + imagePullSecret: {}, + log: { + affinity: { + nodeAffinity: {}, + podAffinity: {}, + podAntiAffinity: {} + }, + annotations: {}, + audit: { + diskCapacityGB: 1 + }, + db: { + affinity: { + nodeAffinity: {}, + podAffinity: {}, + podAntiAffinity: {} + }, + annotations: {}, + env: [], + image: "", + initimage: "", + labels: {}, + nodeSelector: {}, + resources: {}, + securityContext: { + fsGroup: 999, + runAsGroup: 999, + runAsNonRoot: true, + runAsUser: 999 + }, + serviceAccountName: "", + tolerations: [], + volumeClaimTemplate: { + metadata: {}, + spec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "1Gi" + } + }, + storageClassName: "standard" + } + } + }, + env: [], + image: "", + labels: {}, + nodeSelector: {}, + resources: {}, + securityContext: { + fsGroup: 1000, + runAsGroup: 1000, + runAsNonRoot: true, + runAsUser: 1000 + }, + serviceAccountName: "", + tolerations: [] + }, + mountPath: "/export", + podManagementPolicy: "Parallel", + pools: [ + { + name: "pool-0", + servers: 4, + volumeClaimTemplate: { + metadata: { + name: "data" + }, + spec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "2Gi" + } + } + } + }, + volumesPerServer: 2 + } + ], + priorityClassName: "", + prometheus: { + affinity: { + nodeAffinity: {}, + podAffinity: {}, + podAntiAffinity: {} + }, + annotations: {}, + diskCapacityGB: 1, + env: [], + image: "", + initimage: "", + labels: {}, + nodeSelector: {}, + resources: {}, + securityContext: { + fsGroup: 1000, + runAsGroup: 1000, + runAsNonRoot: true, + runAsUser: 1000 + }, + serviceAccountName: "", + sidecarimage: "", + storageClassName: "standard" + }, + requestAutoCert: true, + serviceAccountName: "", + serviceMetadata: { + consoleServiceAnnotations: {}, + consoleServiceLabels: {}, + minioServiceAnnotations: {}, + minioServiceLabels: {} + }, + subPath: "", + users: [ + { + name: "storage-user" + } + ] + } + } + } + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/mongoDB.ts b/server-refactored-v3/src/addons/plugins/mongoDB.ts new file mode 100644 index 00000000..25ff80ab --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/mongoDB.ts @@ -0,0 +1,83 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class MongoDB extends Plugin implements IPlugin { + public id: string = 'mongodb-operator';//same as operator name + public displayName = 'Percona MongoDB' + public icon = '/img/addons/mongo.svg' + public install: string = 'kubectl create -f https://operatorhub.io/install/mongodb-operator.yaml' + public url = 'https://artifacthub.io/packages/olm/community-operators/mongodb-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'MongoDB.metadata.name':{ + type: 'text', + label: 'MongoDB Name', + name: 'MongoDB.metadata.name', + required: true, + default: 'mongodbinstance', + description: 'The name of the MongoDB cluster' + }, + 'MongoDB.spec.storage.storageSize':{ + type: 'text', + label: 'Sorage Size', + name: 'MongoDB.spec.storage.storageSize', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + 'MongoDB.spec.storage.storageClass':{ + type: 'text', + label: 'Sorage Class', + name: 'MongoDB.spec.storage.storageClass', + default: 'standard', + required: true, + description: 'Classname of the storage' + }, + 'mongodbSecret.stringData.password':{ + type: 'text', + label: 'MongoDB Password', + name: 'mongodbSecret.stringData.password', + default: 'changeMe', + required: true, + description: 'Password for MongoDB' + }, + }; + + public env: any[] = [] + + //https://www.convertsimple.com/convert-yaml-to-javascript-object/ + protected additionalResourceDefinitions: Object = { + mongodbSecret: { + apiVersion: "v1", + stringData: { + // TODO - generate a random password or make it configurable + password: "test", + }, + kind: "Secret", + metadata: { + annotations: { + 'meta.helm.sh/release-name': "test", + 'meta.helm.sh/release-namespace': "kubero-dev" + }, + labels: { + 'app.kubernetes.io/managed-by': "Kubero" + }, + name: "mongodb-secret", + }, + type: "Opaque" + } + } + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/mongoDBCluster.ts b/server-refactored-v3/src/addons/plugins/mongoDBCluster.ts new file mode 100644 index 00000000..7864d6a2 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/mongoDBCluster.ts @@ -0,0 +1,54 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class MongoDBCluster extends Plugin implements IPlugin { + public id: string = 'mongodb-operator';//same as operator name + public displayName = 'Percona MongoDB Cluster' + public icon = '/img/addons/mongo.svg' + public install: string = 'kubectl create -f https://operatorhub.io/install/mongodb-operator.yaml' + public url = 'https://artifacthub.io/packages/olm/community-operators/mongodb-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'MongoDBCluster.metadata.name':{ + type: 'text', + label: 'MongoDB Cluster Name', + name: 'metadata.name', + required: true, + default: 'mongodb-cluster', + description: 'The name of the MongoDB cluster' + }, + 'MongoDBCluster.spec.clusterSize':{ + type: 'number', + label: 'Clustersize', + name: 'spec.clusterSize', + default: 3, + required: true, + description: 'Number of Replicasets MongoDB instances in the cluster' + }, + 'MongoDBCluster.spec.storage.storageSize':{ + type: 'text', + label: 'Sorage Size', + name: 'spec.storage.storageSize', + default: '1Gi', + required: true, + description: 'Size of the storage' + }, + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/plugin.interface.ts b/server-refactored-v3/src/addons/plugins/plugin.interface.ts new file mode 100644 index 00000000..7e017cf4 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/plugin.interface.ts @@ -0,0 +1,25 @@ +export interface IPluginFormFields { + type: 'text' | 'number' |'switch' | 'select' | 'select-storageclass', + label: string, + name: string, + required: boolean, + options?: string[], + default: string | number | boolean, + description?: string, +} + +export interface IPlugin { + id: string + enabled: boolean, + beta: boolean, + version: { + latest: string, + installed: string, + }, + description: string, + install: string, + formfields: {[key: string]: IPluginFormFields}, + //crd: KubernetesObject, + resourceDefinitions: any, + artifact_url: string; +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/plugin.ts b/server-refactored-v3/src/addons/plugins/plugin.ts new file mode 100644 index 00000000..aa5adba4 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/plugin.ts @@ -0,0 +1,179 @@ +import axios from 'axios'; +import { KubernetesListObject, KubernetesObject } from '@kubernetes/client-node' +import { Logger } from '@nestjs/common'; + +export interface IPluginFormFields { + type: 'text' | 'number' |'switch' | 'select' | 'select-storageclass', + label: string, + name: string, + required: boolean, + options?: string[], + default: string | number | boolean, + description?: string, +} + +export interface IPlugin { + id: string + enabled: boolean, + beta: boolean, + version: { + latest: string, + installed: string, + }, + description: string, + install: string, + formfields: {[key: string]: IPluginFormFields}, + //crd: KubernetesObject, + resourceDefinitions: any, + artifact_url: string; +} + +export abstract class Plugin { + public plugin?: any; + public id: string = ''; //same as operator name + public enabled: boolean = false; // true if installed + public version: { + latest:string, + installed: string + } = { + 'latest': '0.0.0', // version fetched from artifacthub + 'installed': '0.0.0', // loaded if avialable from local operators + }; + public displayName: string = ''; + public description: string = ''; + public maintainers: Object[] = []; + public links: Object[] = []; + public readme: string = ''; + //public crd: KubernetesObject = {}; // ExampleCRD which will be used as template + protected additionalResourceDefinitions: Object = {}; + public resourceDefinitions: any = {}; // List of CRD to apply + + public artifact_url: string = ''; // Example: https://artifacthub.io/api/v1/packages/olm/community-operators/postgresql + private artefact_data: any = {}; + private operator_data: any = {}; + public kind: string; + + private readonly logger = new Logger(Plugin.name); + + constructor() { + this.kind = this.constructor.name; + } + + public async init(availableCRDs: any) { + + // load data from local Operators + this.operator_data = this.loadOperatorData(availableCRDs); + + // load data from artifacthub + await this.loadMetadataFromArtefacthub(); + + // load CRD from artefacthub, or alterantively from local operator, as a fallback use the CRD from the plugin + this.loadCRD(); + + this.loadAdditionalResourceDefinitions(); + + if (this.enabled) { + this.logger.log("✅ "+this.id + ' ' +this.constructor.name) + //this.logger.debug(this.resourceDefinitions) // debug CRD + } else { + this.logger.log("☑️ "+this.id + ' ' +this.constructor.name) + } + + + } + + private async loadMetadataFromArtefacthub() { + const response = await axios.get(this.artifact_url) + .catch(error => { + this.logger.debug(' failed loading data from artifacthub for '+this.id) + //console.log(error); + } + ); + + // set artifact hub values + if (response?.data && response.data.description) { + //this.displayName = response?.data.displayName; // use the name from the plugin + this.description = response.data.description; + this.maintainers = response.data.maintainers; + this.links = response.data.links; + this.readme = response.data.readme; + this.version.latest = response.data.version; + this.artefact_data = response.data; + } else { + this.logger.debug(" No artefact.io data found for "+this.id) + } + + } + + private loadCRD() { + if (this.resourceDefinitions[this.kind] !== undefined) { + // CRD already loaded from operator + return; + } + if (this.artefact_data.crds === undefined) { + this.logger.debug(" No CRDs defined in artefacthub for "+this.id) + this.loadCRDFromOperatorData(); + return; + } else { + this.loadCRDFromArtefacthubData(); + } + } + + private loadCRDFromArtefacthubData() { + for (const artefactCRD of this.artefact_data.crds) { + if (artefactCRD.kind === this.kind) { + // search in artefact data for the crd + let exampleCRD = this.artefact_data.crds_examples.find((crd: any) => crd.kind === artefactCRD.kind); + + this.resourceDefinitions[this.kind] = exampleCRD; + + //this.displayName = artefactCRD.displayName; // use the name from the plugin + if (artefactCRD.description.length > this.description.length) { + this.description = artefactCRD.description; // use the description from the CRD + } + + break; + } + } + } + + private loadCRDFromOperatorData() { + if (this.operator_data === undefined) { + this.logger.error("No CRDs defined in operator for "+this.id) + return; + } + + const operatorCRDList = this.operator_data.metadata.annotations['alm-examples']; + + if (operatorCRDList === undefined) { + this.logger.error("No CRDs defined in operator for "+this.id) + return; + } + + for (const op of JSON.parse(operatorCRDList)) { + if (op.kind === this.constructor.name) { + //this.crd = op; + this.resourceDefinitions[op.kind] = op; + break; + } + } + } + + private loadOperatorData(availableOperators: any): any { + for (const operatorCRD of availableOperators) { + // console.log(operatorCRD.spec.names.kind, this.constructor.name) // debug CRD + if (operatorCRD.spec.names.kind === this.constructor.name) { + this.enabled = true; + this.version.installed = operatorCRD.spec.version + return operatorCRD; + } + } + return undefined; + } + + private loadAdditionalResourceDefinitions() { + for (const [key, value] of Object.entries(this.additionalResourceDefinitions)) { + this.resourceDefinitions[key] = value; + } + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/postgresCluster.ts b/server-refactored-v3/src/addons/plugins/postgresCluster.ts new file mode 100644 index 00000000..8dee65d6 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/postgresCluster.ts @@ -0,0 +1,190 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class PostgresCluster extends Plugin implements IPlugin { + public id: string = 'postgresoperator';//same as operator name + public displayName = 'Crunchy Postgres Cluster' + public icon = '/img/addons/pgsql.svg' + public install: string = 'kubectl create -f https://operatorhub.io/install/v5/postgresql.yaml' + public url = 'https://artifacthub.io/packages/olm/community-operators/postgresql' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/postgresql' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'PostgresCluster.metadata.name':{ + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'pg-cluster', + description: 'The name of the Redis cluster' + }, + 'PostgresCluster.spec.postgresVersion':{ + type: 'number', + label: 'Postgres Version', + name: 'spec.postgresVersion', + default: 14, + required: true, + description: 'Version of the Running Postgresql' + }, + 'PostgresCluster.spec.instances[0].name':{ + type: 'text', + label: 'Cluster Name', + name: 'spec.instances[0].name', + default: 'instance-1', + required: true, + description: 'Name of the Instance' + }, + 'PostgresCluster.spec.instances[0].replicas':{ + type: 'number', + label: 'Clustersize', + name: 'spec.instances[0].replicas', + default: 1, + required: true, + description: 'Number of Postgres instances in the cluster' + }, + 'PostgresCluster.spec.instances[0].dataVolumeClaimSpec.resources.requests.storage':{ + type: 'text', + label: 'Data Volume size', + name: 'spec.instances[0].dataVolumeClaimSpec.resources.requests.storage', + default: '1Gi', + required: true, + description: 'Number of Postgres instances in the cluster' + }, + }; + + public env: any[] = [ + { + name: "DB_VENDOR", + value: "postgres" + }, + { + name: "DB_ADDR", + valueFrom: { + secretKeyRef: { + name: "hippo-pguser-hippo", + key: "host" + } + } + }, + { + name: "DB_PORT", + valueFrom: { + secretKeyRef: { + name: "hippo-pguser-hippo", + key: "port" + } + } + }, + { + name: "DB_DATABASE", + valueFrom: { + secretKeyRef: { + name: "hippo-pguser-hippo", + key: "dbname" + } + } + }, + { + name: "DB_USER", + valueFrom: { + secretKeyRef: { + name: "hippo-pguser-hippo", + key: "user" + } + } + }, + { + name: "DB_PASSWORD", + valueFrom: { + secretKeyRef: { + name: "hippo-pguser-hippo", + key: "password" + } + } + } + ] + + protected additionalResourceDefinitions: Object = { + // override default resource definitions since example is missing "backups" section + PostgresCluster : { + apiVersion: "postgres-operator.crunchydata.com/v1beta1", + kind: "PostgresCluster", + metadata: { + name: "hippo" + }, + spec: { + image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-14.5-1", + postgresVersion: 14, + instances: [ + { + name: "instance1", + dataVolumeClaimSpec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "1Gi" + } + } + } + } + ], + backups: { + pgbackrest: { + image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.40-1", + repos: [ + { + name: "repo1", + volume: { + volumeClaimSpec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "1Gi" + } + } + } + } + }, + { + name: "repo2", + volume: { + volumeClaimSpec: { + accessModes: [ + "ReadWriteOnce" + ], + resources: { + requests: { + storage: "1Gi" + } + } + } + } + } + ] + } + }, + proxy: { + pgBouncer: { + image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.17-1" + } + } + } + } + } + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/redis.ts b/server-refactored-v3/src/addons/plugins/redis.ts new file mode 100644 index 00000000..56b778f9 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/redis.ts @@ -0,0 +1,81 @@ +import { KubernetesObject } from '@kubernetes/client-node'; +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class Redis extends Plugin implements IPlugin { + public id: string = 'redis-operator';//same as operator name + public displayName = 'Opstree Redis' + public icon = '/img/addons/redis.svg' + public install: string = 'kubectl create -f https://operatorhub.io/install/stable/redis-operator.yaml' + public url = 'https://artifacthub.io/packages/olm/community-operators/redis-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/redis-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'Redis.metadata.name':{ + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'redis-cluster', + description: 'The name of the Redis cluster' + }, + 'Redis.spec.redisExporter.enabled':{ + type: 'switch', + label: 'Exporter enabled', + name: 'spec.redisExporter.enabled', + default: true, + required: true + }, + 'Redis.spec.kubernetesConfig.resources.limits.cpu': { + type: 'text', + label: 'CPU Limit', + name: 'spec.kubernetesConfig.resources.limits.cpu', + default: '101m', + required: true + }, + 'Redis.spec.kubernetesConfig.resources.limits.memory': { + type: 'text', + label:'Memory Limit', + name: 'spec.kubernetesConfig.resources.limits.memory', + default: '128Mi', + required: true + }, + 'Redis.spec.kubernetesConfig.resources.requests.cpu': { + type: 'text', + label: 'CPU Requests', + name: 'spec.kubernetesConfig.resources.requests.cpu', + default: '101m', + required: true + }, + 'Redis.spec.kubernetesConfig.resources.requests.memory': { + type: 'text', + label: 'Memory Requests', + name: 'spec.kubernetesConfig.resources.requests.memory', + default: '128Mi', + required: true + }, + 'Redis.spec.storage.volumeClaimTemplate.spec.resources.requests.storage': { + type: 'text', + label: 'Storage Size', + name: 'spec.storage.volumeClaimTemplate.spec.resources.requests.storage', + default: '1Gi', + required: true + } + }; + + public env: any[] = [] + + protected additionalResourceDefinitions: Object = {} + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/addons/plugins/redisCluster.ts b/server-refactored-v3/src/addons/plugins/redisCluster.ts new file mode 100644 index 00000000..5e042b14 --- /dev/null +++ b/server-refactored-v3/src/addons/plugins/redisCluster.ts @@ -0,0 +1,86 @@ +import {Plugin, IPlugin, IPluginFormFields} from './plugin'; + +// Classname must be same as the CRD's Name +export class RedisCluster extends Plugin implements IPlugin { + public id: string = 'redis-operator';//same as operator name + public displayName = 'Opstree Redis Cluster' + public icon = '/img/addons/redis.svg' + public install: string = 'kubectl create -f https://operatorhub.io/install/stable/redis-operator.yaml' + public url = 'https://artifacthub.io/packages/olm/community-operators/redis-operator' + public docs = [ + { + title: 'Kubero Docs', url: '' + } + ] + public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/redis-operator' + public beta: boolean = true; + + public formfields: {[key: string]: IPluginFormFields} = { + 'RedisCluster.metadata.name':{ + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'redis-cluster', + description: 'The name of the Redis cluster' + }, + 'RedisCluster.spec.clusterSize':{ + type: 'number', + label: 'Clustersize', + name: 'spec.clusterSize', + default: 3, + required: true, + description: 'Number of Redis nodes in the cluster' + }, + 'RedisCluster.spec.redisExporter.enabled':{ + type: 'switch', + label: 'Exporter enabled', + name: 'spec.redisExporter.enabled', + default: true, + required: true + }, + 'RedisCluster.spec.kubernetesConfig.resources.limits.cpu': { + type: 'text', + label: 'CPU Limit', + name: 'spec.kubernetesConfig.resources.limits.cpu', + default: '101m', + required: true + }, + 'RedisCluster.spec.kubernetesConfig.resources.limits.memory': { + type: 'text', + label:'Memory Limit', + name: 'spec.kubernetesConfig.resources.limits.memory', + default: '128Mi', + required: true + }, + 'RedisCluster.spec.kubernetesConfig.resources.requests.cpu': { + type: 'text', + label: 'CPU Requests', + name: 'spec.kubernetesConfig.resources.requests.cpu', + default: '101m', + required: true + }, + 'RedisCluster.spec.kubernetesConfig.resources.requests.memory': { + type: 'text', + label: 'Memory Requests', + name: 'spec.kubernetesConfig.resources.requests.memory', + default: '128Mi', + required: true + }, + 'RedisCluster.spec.storage.volumeClaimTemplate.spec.resources.requests.storage': { + type: 'text', + label: 'Storage Size', + name: 'spec.storage.volumeClaimTemplate.spec.resources.requests.storage', + default: '1Gi', + required: true + } + }; + + public env: any[] = [] + + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 578ac9ce..413aa2a0 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -17,6 +17,8 @@ import { LogsModule } from './logs/logs.module'; import { DeploymentsModule } from './deployments/deployments.module'; import { CoreModule } from './core/core.module'; import { KubernetesModule } from './kubernetes/kubernetes.module'; +import { AuditModule } from './audit/audit.module'; +import { AddonsModule } from './addons/addons.module'; @Module({ @@ -38,6 +40,8 @@ import { KubernetesModule } from './kubernetes/kubernetes.module'; LogsModule, DeploymentsModule, KubernetesModule, + AuditModule, + AddonsModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/audit/audit.controller.spec.ts b/server-refactored-v3/src/audit/audit.controller.spec.ts new file mode 100644 index 00000000..e3548b40 --- /dev/null +++ b/server-refactored-v3/src/audit/audit.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuditController } from './audit.controller'; + +describe('AuditController', () => { + let controller: AuditController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AuditController], + }).compile(); + + controller = module.get(AuditController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/audit/audit.controller.ts b/server-refactored-v3/src/audit/audit.controller.ts new file mode 100644 index 00000000..bd774f82 --- /dev/null +++ b/server-refactored-v3/src/audit/audit.controller.ts @@ -0,0 +1,27 @@ +import { Controller, DefaultValuePipe, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { AuditService } from './audit.service'; +import { ApiOperation } from '@nestjs/swagger'; + +@Controller({ path: 'api/audit', version: '1' }) +export class AuditController { + constructor(private readonly auditService: AuditService) {} + + @ApiOperation({ summary: 'Get all audit entries for a specific app' }) + @Get('/:pipeline/:phase/:app') + async getAudit( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Query('limit', new DefaultValuePipe('100'), new ParseIntPipe({ optional: true })) limit: number, + ) { + return this.auditService.getAppEntries(pipeline, phase, app, limit); + } + + @ApiOperation({ summary: 'Get all audit entries' }) + @Get('/all') + async getAuditAll( + @Query('limit', new DefaultValuePipe('100'), new ParseIntPipe({ optional: true })) limit: number, + ) { + return this.auditService.get(limit); + } +} diff --git a/server-refactored-v3/src/audit/audit.interface.ts b/server-refactored-v3/src/audit/audit.interface.ts new file mode 100644 index 00000000..fba8887e --- /dev/null +++ b/server-refactored-v3/src/audit/audit.interface.ts @@ -0,0 +1,11 @@ +export interface AuditEntry { + user: string, + severity: "normal" | "info" | "warning" | "critical" | "error" | "unknown", + action: string, + resource: "system" | "app" | "pipeline" | "phase" | "namespace" | "build" | "addon" | "settings" | "user" | "events" | "security" | "templates" | "config" | "addons" | "kubernetes" | "unknown", + namespace: string, + phase: string, + app: string, + pipeline: string, + message: string, +} \ No newline at end of file diff --git a/server-refactored-v3/src/audit/audit.module.ts b/server-refactored-v3/src/audit/audit.module.ts new file mode 100644 index 00000000..f321fa7d --- /dev/null +++ b/server-refactored-v3/src/audit/audit.module.ts @@ -0,0 +1,10 @@ +import { Module, Global } from '@nestjs/common'; +import { AuditService } from './audit.service'; +import { AuditController } from './audit.controller'; + +@Global() +@Module({ + providers: [AuditService], + controllers: [AuditController] +}) +export class AuditModule {} diff --git a/server-refactored-v3/src/audit/audit.service.spec.ts b/server-refactored-v3/src/audit/audit.service.spec.ts new file mode 100644 index 00000000..fcd49655 --- /dev/null +++ b/server-refactored-v3/src/audit/audit.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuditService } from './audit.service'; + +describe('AuditService', () => { + let service: AuditService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AuditService], + }).compile(); + + service = module.get(AuditService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/audit/audit.service.ts b/server-refactored-v3/src/audit/audit.service.ts new file mode 100644 index 00000000..7c4f06ca --- /dev/null +++ b/server-refactored-v3/src/audit/audit.service.ts @@ -0,0 +1,256 @@ +import { Injectable } from '@nestjs/common'; +import { AuditEntry } from './audit.interface'; +import { Logger } from '@nestjs/common'; +​import { Database } from 'sqlite3'; +import * as fs from 'fs'; + +@Injectable() +export class AuditService { + + private db: Database | undefined; + private logmaxbackups: number = 1000; + private enabled: boolean = true; + private dbpath: string = './db'; + private readonly logger = new Logger(AuditService.name); + + constructor() { + this.dbpath = process.env.KUBERO_AUDIT_DB_PATH || './db'; + this.logmaxbackups = process.env.KUBERO_AUDIT_LIMIT ? parseInt(process.env.KUBERO_AUDIT_LIMIT) : 1000; + + if (process.env.KUBERO_AUDIT !== 'true') { + this.enabled = false; + Logger.log('⏸️ Audit logging not enabled', 'Feature'); + return; + } + + this.init() + } + + public async init() { + if (!this.enabled) { + return; + } + + if (!fs.existsSync(this.dbpath)){ + try { + fs.mkdirSync(this.dbpath); + } catch (error) { + console.error(error); + } + } + this.db = new Database(this.dbpath + '/kubero.db', (err) => { + if (err) { + this.logger.error('❌ Audit logging failed to create local sqlite database', err.message); + } + Logger.log('✅ Audit logging enabled', 'Feature'); + this.createTables(); + }); + } + + private createTables() { + this.db?.run(`CREATE TABLE IF NOT EXISTS audit ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + user TEXT, + action TEXT, + namespace TEXT, + phase TEXT, + app TEXT, + pipeline TEXT, + resource TEXT, + message TEXT + )`, (err) => { + if (err) { + this.logger.error(err); + } + }); + } + + public logDelayed(entry: AuditEntry, delay: number = 1000) { + setTimeout(() => { + this.log(entry); + }, delay); + } + + public log(entry: AuditEntry) { + //this.logger.debug(entry) + if (!this.enabled) { + return; + } + this.db?.run(`INSERT INTO audit ( + user, + action, + namespace, + phase, + app, + pipeline, + resource, + message + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ + entry.user, + entry.action, + entry.namespace, + entry.phase, + entry.app, + entry.pipeline, + entry.resource, + entry.message + ], (err) => { + if (err) { + this.logger.error(err); + } + } + ); + + this.limit(this.logmaxbackups); + } + + public get(limit: number = 100): Promise { + if (!this.enabled) { + return new Promise((resolve) => { + resolve([]); + }); + } + return new Promise((resolve, reject) => { + this.db?.all(`SELECT * FROM audit ORDER BY timestamp DESC LIMIT ?`, [limit], (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }); + }); + } + + public getFiltered(limit: number = 100, filter: string = ''): Promise { + if (!this.enabled) { + return new Promise((resolve) => { + resolve([]); + }); + } + return new Promise((resolve, reject) => { + this.db?.all(`SELECT * FROM audit WHERE message LIKE ? ORDER BY timestamp DESC LIMIT ?`, ['%'+filter+'%', limit], (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }); + }); + } + + public getAppEntries(pipeline: string, phase: string, app: string, limit: number = 100): Promise { + if (!this.enabled) { + return new Promise((resolve) => { + resolve([]); + }); + } + return new Promise((resolve, reject) => { + this.db?.all(`SELECT * FROM audit WHERE pipeline = ? AND phase = ? AND app = ? ORDER BY timestamp DESC LIMIT ?`, [pipeline, phase, app, limit], (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }); + }); + }; + + public getPhaseEntries(phase: string, limit: number = 100): Promise { + if (!this.enabled) { + return new Promise((resolve, reject) => { + resolve([]); + }); + } + return new Promise((resolve, reject) => { + this.db?.all(`SELECT * FROM audit WHERE phase = ? ORDER BY timestamp DESC LIMIT ?`, [phase, limit], (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }); + }); + }; + + public getPipelineEntries(pipeline: string, limit: number = 100): Promise { + if (!this.enabled) { + return new Promise((resolve, reject) => { + resolve([]); + }); + } + return new Promise((resolve, reject) => { + this.db?.all(`SELECT * FROM audit WHERE pipeline = ? ORDER BY timestamp DESC LIMIT ?`, [pipeline, limit], (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }); + }); + }; + + private flush(): Promise { + return new Promise((resolve, reject) => { + this.db?.run(`DELETE FROM audit`, (err) => { + if (err) { + reject(err); + } + resolve(); + }); + }); + } + + private close(): Promise { + return new Promise((resolve, reject) => { + this.db?.close((err) => { + if (err) { + reject(err); + } + resolve(); + }); + }); + } + + public async reset(): Promise { + if (!this.enabled) { + return; + } + await this.flush(); + await this.close(); + fs.unlinkSync('./db/kubero.db'); + this.db = new Database('./db/kubero.db', (err) => { + if (err) { + this.logger.error(err.message); + } + this.logger.log('Connected to the kubero database.'); + }); + this.createTables(); + } + + // remove the oldest entries from database if the limit is reached + private limit = (limit: number = 1000) => { + this.db?.run(`DELETE FROM audit WHERE id IN (SELECT id FROM audit ORDER BY timestamp DESC LIMIT -1 OFFSET ?)`, [limit], (err) => { + if (err) { + this.logger.error(err); + } + }) + } + + public count(): Promise { + if (!this.enabled) { + return new Promise((resolve, reject) => { + resolve(0); + }); + } + return new Promise((resolve, reject) => { + this.db?.get(`SELECT COUNT(*) as entries FROM audit`, (err, row) => { + if (err) { + reject(err); + } + resolve((row as any)['entries'] as number); + }); + }); + } + + public getAuditEnabled(): boolean { + return this.enabled; + } + + +} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index bc21e5fe..3feb326e 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -6,10 +6,12 @@ import { SettingsService } from '../settings/settings.service'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; import { AuthController } from './auth.controller'; +import { AuditService } from 'src/audit/audit.service'; +import { SettingsModule } from 'src/settings/settings.module'; @Module({ - imports: [UsersModule, PassportModule], - providers: [AuthService, LocalStrategy, KubernetesModule, SettingsService], + imports: [UsersModule, PassportModule ], + providers: [AuthService, LocalStrategy, KubernetesModule, AuditService, SettingsService], controllers: [AuthController], }) export class AuthModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/auth.service.ts b/server-refactored-v3/src/auth/auth.service.ts index acabc382..470197f3 100644 --- a/server-refactored-v3/src/auth/auth.service.ts +++ b/server-refactored-v3/src/auth/auth.service.ts @@ -2,13 +2,15 @@ import { Injectable, Request } from '@nestjs/common'; import { UsersService } from '../users/users.service'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { SettingsService } from '../settings/settings.service'; +import { AuditService } from '../audit/audit.service'; @Injectable() export class AuthService { constructor( private usersService: UsersService, private kubectl: KubernetesService, - private settingsService: SettingsService + private settingsService: SettingsService, + private auditService: AuditService, ) {} async validateUser(username: string, pass: string): Promise { @@ -36,11 +38,11 @@ export class AuthService { let message = { "isAuthenticated": isAuthenticated, "version": process.env.npm_package_version, - "kubernetesVersion": this.kubectl.getKubeVersion(), + "kubernetesVersion": this.kubectl.getKubernetesVersion(), "operatorVersion": this.kubectl.getOperatorVersion(), "buildPipeline": this.settingsService.getBuildpipelineEnabled(), "templatesEnabled": this.settingsService.getTemplateEnabled(), - //"auditEnabled": req.app.locals.audit.getAuditEnabled(), + "auditEnabled": this.auditService.getAuditEnabled(), "adminDisabled": this.settingsService.checkAdminDisabled(), "consoleEnabled": this.settingsService.getConsoleEnabled(), "metricsEnabled": this.settingsService.getMetricsEnabled(), diff --git a/server-refactored-v3/src/kubernetes/kubernetes.service.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts index 0159eb19..ba4e40bf 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.service.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -1,4 +1,4 @@ -import { Global, Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList} from './kubernetes.interface'; import { IPipeline, } from '../pipelines/pipelines.interface'; import { KubectlPipeline } from '../pipelines/pipeline/pipeline'; @@ -129,6 +129,10 @@ export class KubernetesService { return this.kubeVersion; } + public getKubernetesVersion(): string { + return this.kubeVersion?.gitVersion || 'unknown'; + } + public async loadKubeVersion(): Promise{ // TODO and WARNING: This does not respect the context set by the user! try { diff --git a/server-refactored-v3/src/logger/logger.ts b/server-refactored-v3/src/logger/logger.ts index d360d215..7f90baae 100644 --- a/server-refactored-v3/src/logger/logger.ts +++ b/server-refactored-v3/src/logger/logger.ts @@ -13,9 +13,9 @@ export class CustomConsoleLogger extends ConsoleLogger { 'InstanceLoader', 'RoutesResolver', 'RouterExplorer', - //'NestFactory', // I prefer not including this one - //'NestApplication', - //'WebSocketsController', + 'NestFactory', + 'NestApplication', + 'WebSocketsController', ] log(_: any, context?: string): void { diff --git a/server-refactored-v3/src/pipelines/pipelines.controller.spec.ts b/server-refactored-v3/src/pipelines/pipelines.controller.spec.ts new file mode 100644 index 00000000..237bdb11 --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipelines.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PipelinesController } from './pipelines.controller'; + +describe('PipelinesController', () => { + let controller: PipelinesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [PipelinesController], + }).compile(); + + controller = module.get(PipelinesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/pipelines/pipelines.controller.ts b/server-refactored-v3/src/pipelines/pipelines.controller.ts new file mode 100644 index 00000000..613d20bd --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipelines.controller.ts @@ -0,0 +1,45 @@ +import { Controller, Delete, Get, Post, Put } from '@nestjs/common'; +import { PipelinesService } from './pipelines.service'; +import { ApiOperation, ApiResponse } from '@nestjs/swagger'; + +@Controller({ path: 'api/pipelines', version: '1' }) +export class PipelinesController { + + constructor(private pipelinesService: PipelinesService) {} + + @ApiOperation({ summary: 'Get all pipelines' }) + @Get('/') + async getPipelines() { + return this.pipelinesService.listPipelines(); + } + + @ApiOperation({ summary: 'Create a new pipeline' }) + @Post('/:pipeline') + async createPipeline() { + return 'Pipeline updated'; + } + + @ApiOperation({ summary: 'Get a pipeline' }) + @Get('/:pipeline') + async getPipeline() { + return 'Pipeline'; + } + + @ApiOperation({ summary: 'Update a pipeline' }) + @Put('/:pipeline') + async updatePipeline() { + return 'Pipeline updated'; + } + + @ApiOperation({ summary: 'Delete a pipeline' }) + @Delete('/:pipeline') + async deletePipeline() { + return 'Pipeline deleted'; + } + + @ApiOperation({ summary: 'Get all apps for a pipeline' }) + @Get('/:pipeline/apps') + async getPipelineApps() { + return 'Pipeline apps'; + } +} diff --git a/server-refactored-v3/src/pipelines/pipelines.interface.ts b/server-refactored-v3/src/pipelines/pipelines.interface.ts index ddfe62b3..6e0bb7b3 100644 --- a/server-refactored-v3/src/pipelines/pipelines.interface.ts +++ b/server-refactored-v3/src/pipelines/pipelines.interface.ts @@ -15,6 +15,11 @@ export interface IPipeline { buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks', resourceVersion?: string; // required to update resource, not part of spec } + +export interface IPipelineList { + items: IPipeline[], +} + export interface IgitLink { keys: { priv?: string, diff --git a/server-refactored-v3/src/pipelines/pipelines.module.ts b/server-refactored-v3/src/pipelines/pipelines.module.ts index 7acc88a7..e4317d13 100644 --- a/server-refactored-v3/src/pipelines/pipelines.module.ts +++ b/server-refactored-v3/src/pipelines/pipelines.module.ts @@ -1,4 +1,9 @@ import { Module } from '@nestjs/common'; +import { PipelinesController } from './pipelines.controller'; +import { PipelinesService } from './pipelines.service'; -@Module({}) +@Module({ + controllers: [PipelinesController], + providers: [PipelinesService] +}) export class PipelinesModule {} diff --git a/server-refactored-v3/src/pipelines/pipelines.service.spec.ts b/server-refactored-v3/src/pipelines/pipelines.service.spec.ts new file mode 100644 index 00000000..e7a30b62 --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipelines.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PipelinesService } from './pipelines.service'; + +describe('PipelinesService', () => { + let service: PipelinesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [PipelinesService], + }).compile(); + + service = module.get(PipelinesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/pipelines/pipelines.service.ts b/server-refactored-v3/src/pipelines/pipelines.service.ts new file mode 100644 index 00000000..34d4f3b5 --- /dev/null +++ b/server-refactored-v3/src/pipelines/pipelines.service.ts @@ -0,0 +1,22 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { IPipelineList } from './pipelines.interface'; +import { KubernetesService } from 'src/kubernetes/kubernetes.service'; + +@Injectable() +export class PipelinesService { + private readonly logger = new Logger(PipelinesService.name); + + constructor(private kubectl: KubernetesService) {} + + public async listPipelines(): Promise { + let pipelines = await this.kubectl.getPipelinesList(); + const ret: IPipelineList = { + items: new Array() + } + for (const pipeline of pipelines.items) { + this.logger.debug('listed pipeline: '+pipeline.spec.name); + ret.items.push(pipeline.spec); + } + return ret; + } +} diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts index 16c1c390..602d490b 100644 --- a/server-refactored-v3/src/settings/settings.controller.ts +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -1,21 +1,26 @@ import { Controller, Get } from '@nestjs/common'; //import { ApiTags } from '@nestjs/swagger'; import { SettingsService } from './settings.service'; +import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/settings', version: '1' }) export class SettingsController { constructor(private readonly settingsService: SettingsService) {} + + @ApiOperation({ summary: 'Get the Kubero settings' }) @Get('/') async getSettings() { return this.settingsService.getSettings(); } + @ApiOperation({ summary: 'Get the banner informations' }) @Get('/banner') async getBanner() { return this.settingsService.getBanner(); } + @ApiOperation({ summary: 'Get a list of allredy taken domains on this Kubernets cluster' }) @Get('/domains') async getDomains() { return this.settingsService.getDomains(); diff --git a/server-refactored-v3/src/settings/settings.interface.ts b/server-refactored-v3/src/settings/settings.interface.ts index 3a53b854..b55cfa52 100644 --- a/server-refactored-v3/src/settings/settings.interface.ts +++ b/server-refactored-v3/src/settings/settings.interface.ts @@ -33,6 +33,42 @@ export interface IKuberoConfig { } } +export type IKuberoCRD = { + kubero: { + debug: string + namespace: string + context: string + webhook_url: string + auth: { + github: { + enabled: boolean + id: string + callbackUrl: string + org: string + } + oauth2: { + enabled: boolean + name: string + id: string + authUrl: string + tokenUrl: string + secret: string + callbackUrl: string + scope: string + } + } + auditLogs: { + enabled: boolean + storageClassName: any + accessModes: Array + size: string + limit: number + } + config: IKuberoConfig + } + } + + interface INotificationConfig{ enabled: boolean; name: string; diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 33395e11..e38e8217 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -1,5 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -import { IKuberoConfig } from './settings.interface'; +import { IKuberoCRD, IKuberoConfig } from './settings.interface'; import { KuberoConfig } from './kubero-config/kubero-config'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { readFileSync, writeFileSync } from 'fs'; @@ -41,7 +41,8 @@ export class SettingsService { return new KuberoConfig(new Object() as IKuberoConfig) } - const configMap = new KuberoConfig(await this.readConfig()) + const oo = await this.readConfig() + const configMap = new KuberoConfig(oo) let config: any = {} config.settings = configMap @@ -63,10 +64,9 @@ export class SettingsService { } private reloadRunningConfig(): void { - - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - this.kubectl.getKuberoConfig(namespace).then((kuberoes) => { - this.runningConfig = kuberoes.spec + this.readConfig().then((config) => { + this.logger.debug('Kubero config loaded') + this.runningConfig = config }).catch((error) => { this.logger.error('Error reading kuberoes config') this.logger.error(error) @@ -74,19 +74,20 @@ export class SettingsService { } private async readConfig(): Promise { - if (process.env.NODE_ENV === "production") { - return await this.readConfigFromKubernetes() + const kuberoCRD = await this.readConfigFromKubernetes() + return kuberoCRD.kubero.config } else { + console.log("aaaa", this.readConfigFromFS()) return this.readConfigFromFS() } } - private async readConfigFromKubernetes(): Promise { + private async readConfigFromKubernetes(): Promise { const namespace = process.env.KUBERO_NAMESPACE || "kubero" let kuberoes = await this.kubectl.getKuberoConfig(namespace) - return kuberoes.spec.kubero.config + return kuberoes.spec } private readConfigFromFS(): IKuberoConfig { @@ -136,7 +137,7 @@ export class SettingsService { const kuberoes = await this.kubectl.getKuberoConfig(namespace) registry = kuberoes.spec.registry } catch (error) { - console.log("Error getting kuberoes config") + this.logger.error("Error getting kuberoes config") } return registry } @@ -245,6 +246,7 @@ export class SettingsService { } getTemplateEnabled(){ + console.log("runningConfig", this.runningConfig) return this.runningConfig.templates?.enabled || false } diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 32e386b0..8f3be3fe 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -2161,6 +2161,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + b4a@^1.6.4: version "1.6.7" resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" @@ -3422,6 +3431,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + foreground-child@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" @@ -5334,6 +5348,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +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" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.28: version "1.15.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 5455d807..9b55cfb2 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -257,7 +257,7 @@ export class Kubero { this.notification.send(m, this._io); } - + //Migrated to pipelines public async listPipelines(): Promise { debug.debug('listPipelines'); let pipelines = await this.kubectl.getPipelinesList(); diff --git a/server/src/types.ts b/server/src/types.ts index 7c8c82ec..8be936da 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -241,6 +241,7 @@ export interface IgitLink { webhook: object; } +//Migrated to pipelines export interface IPipelineList { items: IPipeline[], } From bbde7bef98c0a0f729b2e0634dca3d233ffe356f Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 7 Feb 2025 04:32:31 +0100 Subject: [PATCH 19/65] Migrate templates --- .../src/settings/settings.controller.ts | 6 ++++++ .../src/settings/settings.service.ts | 5 ++++- .../src/templates/template.spec.ts | 3 ++- .../src/templates/templates.controller.spec.ts | 18 ++++++++++++++++++ .../src/templates/templates.controller.ts | 11 +++++++++++ .../src/templates/templates.module.ts | 5 ++++- 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 server-refactored-v3/src/templates/templates.controller.spec.ts create mode 100644 server-refactored-v3/src/templates/templates.controller.ts diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts index 602d490b..6acef4c4 100644 --- a/server-refactored-v3/src/settings/settings.controller.ts +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -25,4 +25,10 @@ export class SettingsController { async getDomains() { return this.settingsService.getDomains(); } + + @ApiOperation({ summary: 'Get the templates settings' }) + @Get('/templates') + async getTemplates() { + return this.settingsService.getTemplateConfig(); + } } diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index e38e8217..6d9c9fc2 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -246,10 +246,13 @@ export class SettingsService { } getTemplateEnabled(){ - console.log("runningConfig", this.runningConfig) return this.runningConfig.templates?.enabled || false } + public async getTemplateConfig() { + return this.runningConfig.templates + } + getConsoleEnabled(){ if (this.runningConfig.kubero?.console?.enabled == undefined) { return false; diff --git a/server-refactored-v3/src/templates/template.spec.ts b/server-refactored-v3/src/templates/template.spec.ts index dee9efed..6e4bc026 100644 --- a/server-refactored-v3/src/templates/template.spec.ts +++ b/server-refactored-v3/src/templates/template.spec.ts @@ -1,7 +1,8 @@ +import { IApp } from 'src/apps/apps.interface'; import { Template } from './template'; describe('Template', () => { it('should be defined', () => { - expect(new Template()).toBeDefined(); + expect(new Template({} as IApp)).toBeDefined(); }); }); diff --git a/server-refactored-v3/src/templates/templates.controller.spec.ts b/server-refactored-v3/src/templates/templates.controller.spec.ts new file mode 100644 index 00000000..7017523b --- /dev/null +++ b/server-refactored-v3/src/templates/templates.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TemplatesController } from './templates.controller'; + +describe('TemplatesController', () => { + let controller: TemplatesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [TemplatesController], + }).compile(); + + controller = module.get(TemplatesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/templates/templates.controller.ts b/server-refactored-v3/src/templates/templates.controller.ts new file mode 100644 index 00000000..7dcaca63 --- /dev/null +++ b/server-refactored-v3/src/templates/templates.controller.ts @@ -0,0 +1,11 @@ +import { Controller, Get } from '@nestjs/common'; + +@Controller('templates') +export class TemplatesController { + constructor() {} + + @Get('/catalogs') + async getTemplates() { + return 'getTemplates'; + } +} diff --git a/server-refactored-v3/src/templates/templates.module.ts b/server-refactored-v3/src/templates/templates.module.ts index 3bca3414..0cdf3ee9 100644 --- a/server-refactored-v3/src/templates/templates.module.ts +++ b/server-refactored-v3/src/templates/templates.module.ts @@ -1,4 +1,7 @@ import { Module } from '@nestjs/common'; +import { TemplatesController } from './templates.controller'; -@Module({}) +@Module({ + controllers: [TemplatesController] +}) export class TemplatesModule {} From 5e57db111c94dc0dafe75e0247633270aae077cd Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 7 Feb 2025 04:33:32 +0100 Subject: [PATCH 20/65] Migrate templates --- client/src/components/templates/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/templates/index.vue b/client/src/components/templates/index.vue index 6301c3e2..0dceae2d 100644 --- a/client/src/components/templates/index.vue +++ b/client/src/components/templates/index.vue @@ -269,7 +269,7 @@ export default defineComponent({ }, loadCatalogs(catalogId: number) { const self = this; - axios.get(`/api/config/catalogs`) + axios.get(`/api/settings/templates`) .then(response => { self.templates = response.data as Templates; if (self.templates.catalogs.length > 0 && self.templates.enabled == true) { From 0b5bf42ac2da44a5f412b916a538b3a490d715dc Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 7 Feb 2025 21:07:18 +0100 Subject: [PATCH 21/65] migrate activy view --- .../src/app.controller.spec.ts | 5 ----- server-refactored-v3/src/app.controller.ts | 17 -------------- server-refactored-v3/src/app.service.ts | 5 ----- .../src/audit/audit.controller.ts | 2 +- .../src/audit/audit.service.ts | 22 +++++++++++++++---- server-refactored-v3/src/auth/auth.module.ts | 1 - 6 files changed, 19 insertions(+), 33 deletions(-) diff --git a/server-refactored-v3/src/app.controller.spec.ts b/server-refactored-v3/src/app.controller.spec.ts index d22f3890..c86814d2 100644 --- a/server-refactored-v3/src/app.controller.spec.ts +++ b/server-refactored-v3/src/app.controller.spec.ts @@ -14,9 +14,4 @@ describe('AppController', () => { appController = app.get(AppController); }); - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); }); diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts index e4b6c09c..51cc1471 100644 --- a/server-refactored-v3/src/app.controller.ts +++ b/server-refactored-v3/src/app.controller.ts @@ -6,21 +6,4 @@ import { AuthGuard } from '@nestjs/passport'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} - -/* - @UseGuards(AuthGuard('local')) - @Get('/hello') - getHello(): string { - return this.appService.getHello(); - } -/* - @All('*') - @HttpCode(404) - catchAll(@Res() res: Response) { - //catchAll() { - res.status(404); - res.json({"statusCode":404,"message":"Not Found"}); - //return '{"statusCode":404,"message":"Not Found"}'; - } -*/ } diff --git a/server-refactored-v3/src/app.service.ts b/server-refactored-v3/src/app.service.ts index 46ecab9f..a031ef59 100644 --- a/server-refactored-v3/src/app.service.ts +++ b/server-refactored-v3/src/app.service.ts @@ -2,9 +2,4 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { - /* - getHello(): string { - return 'Hello World!'; - } - */ } diff --git a/server-refactored-v3/src/audit/audit.controller.ts b/server-refactored-v3/src/audit/audit.controller.ts index bd774f82..b4da3b98 100644 --- a/server-refactored-v3/src/audit/audit.controller.ts +++ b/server-refactored-v3/src/audit/audit.controller.ts @@ -18,7 +18,7 @@ export class AuditController { } @ApiOperation({ summary: 'Get all audit entries' }) - @Get('/all') + @Get('/') async getAuditAll( @Query('limit', new DefaultValuePipe('100'), new ParseIntPipe({ optional: true })) limit: number, ) { diff --git a/server-refactored-v3/src/audit/audit.service.ts b/server-refactored-v3/src/audit/audit.service.ts index 7c4f06ca..7cf89034 100644 --- a/server-refactored-v3/src/audit/audit.service.ts +++ b/server-refactored-v3/src/audit/audit.service.ts @@ -22,7 +22,6 @@ export class AuditService { Logger.log('⏸️ Audit logging not enabled', 'Feature'); return; } - this.init() } @@ -44,6 +43,21 @@ export class AuditService { } Logger.log('✅ Audit logging enabled', 'Feature'); this.createTables(); + + const auditEntry: AuditEntry = { + user: 'kubero', + severity: 'normal', + action: 'start', + namespace: '', + phase: '', + app: '', + pipeline: '', + resource: 'system', + message: 'server started', + } + + this.log(auditEntry); + }); } @@ -105,10 +119,10 @@ export class AuditService { this.limit(this.logmaxbackups); } - public get(limit: number = 100): Promise { + public get(limit: number = 100): Promise<{audit: AuditEntry[], count: number, limit: number}> { if (!this.enabled) { return new Promise((resolve) => { - resolve([]); + resolve({audit: [], count: 0, limit: limit}); }); } return new Promise((resolve, reject) => { @@ -116,7 +130,7 @@ export class AuditService { if (err) { reject(err); } - resolve(rows as AuditEntry[]); + resolve({audit: rows as AuditEntry[], count: rows.length, limit: limit}); }); }); } diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index 3feb326e..e8136ff4 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -7,7 +7,6 @@ import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; import { AuthController } from './auth.controller'; import { AuditService } from 'src/audit/audit.service'; -import { SettingsModule } from 'src/settings/settings.module'; @Module({ imports: [UsersModule, PassportModule ], From 92ab455019ecbac438a7b2af4596017fc3e9afbf Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 7 Feb 2025 23:44:01 +0100 Subject: [PATCH 22/65] migrated pipeline form --- client/src/components/pipelines/form.vue | 10 +- server-refactored-v3/package.json | 6 + .../src/repo/git/bitbucket.ts | 375 ++++++++++++++++ server-refactored-v3/src/repo/git/gitea.ts | 352 +++++++++++++++ server-refactored-v3/src/repo/git/github.ts | 401 ++++++++++++++++++ server-refactored-v3/src/repo/git/gitlab.ts | 378 +++++++++++++++++ server-refactored-v3/src/repo/git/gogs.ts | 331 +++++++++++++++ .../src/repo/git/repo.test.ts | 40 ++ server-refactored-v3/src/repo/git/repo.ts | 136 ++++++ server-refactored-v3/src/repo/git/types.ts | 80 ++++ .../src/repo/repo.controller.spec.ts | 18 + .../src/repo/repo.controller.ts | 24 ++ .../src/repo/repo.interface.ts | 7 + server-refactored-v3/src/repo/repo.module.ts | 7 +- .../src/repo/repo.service.spec.ts | 18 + server-refactored-v3/src/repo/repo.service.ts | 219 ++++++++++ .../src/settings/settings.controller.ts | 29 ++ .../src/settings/settings.interface.ts | 18 +- .../src/settings/settings.service.ts | 38 +- server-refactored-v3/yarn.lock | 321 +++++++++++++- server/src/kubero.ts | 1 + server/src/types.ts | 1 + 22 files changed, 2790 insertions(+), 20 deletions(-) create mode 100644 server-refactored-v3/src/repo/git/bitbucket.ts create mode 100644 server-refactored-v3/src/repo/git/gitea.ts create mode 100644 server-refactored-v3/src/repo/git/github.ts create mode 100644 server-refactored-v3/src/repo/git/gitlab.ts create mode 100644 server-refactored-v3/src/repo/git/gogs.ts create mode 100644 server-refactored-v3/src/repo/git/repo.test.ts create mode 100644 server-refactored-v3/src/repo/git/repo.ts create mode 100644 server-refactored-v3/src/repo/git/types.ts create mode 100644 server-refactored-v3/src/repo/repo.controller.spec.ts create mode 100644 server-refactored-v3/src/repo/repo.controller.ts create mode 100644 server-refactored-v3/src/repo/repo.interface.ts create mode 100644 server-refactored-v3/src/repo/repo.service.spec.ts create mode 100644 server-refactored-v3/src/repo/repo.service.ts diff --git a/client/src/components/pipelines/form.vue b/client/src/components/pipelines/form.vue index 7a3badb2..3919f911 100644 --- a/client/src/components/pipelines/form.vue +++ b/client/src/components/pipelines/form.vue @@ -713,7 +713,7 @@ export default defineComponent({ this.buildpack = buildpack; }, getContextList() { - axios.get('/api/config/k8s/context').then(response => { + axios.get('/api/settings/contexts').then(response => { for (let i = 0; i < response.data.length; i++) { this.contextList.push(response.data[i].name); } @@ -726,12 +726,12 @@ export default defineComponent({ }); }, listRepositories() { - axios.get('/api/config/repositories').then(response => { + axios.get('/api/repo/providers').then(response => { this.repositoriesList = response.data }); }, listBuildpacks() { - axios.get('/api/config/buildpacks').then(response => { + axios.get('/api/settings/runpacks').then(response => { for (let i = 0; i < response.data.length; i++) { this.buildpackList.push({ text: response.data[i].name, @@ -805,7 +805,7 @@ export default defineComponent({ }, loadRepository() { - axios.get(`/api/repo/${this.repotab}/list`) + axios.get(`/api/repo/${this.repotab}/repositories`) .then(response => { this.gitrepoItems = response.data; }).catch(error => { @@ -815,7 +815,7 @@ export default defineComponent({ loadDefaultregistry() { if (this.pipeline === 'new') { - axios.get(`/api/config/registry`) + axios.get(`/api/settings/registry`) .then(response => { if (response.data.host && response.data.account.username && response.data.account.password) { this.registry ={ diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index 89698db9..4aa58e50 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@kubernetes/client-node": "^0.22.3", + "@nerdvision/gitlab-js": "^1.0.0-alpha.12", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/passport": "^11.0.5", @@ -31,10 +32,15 @@ "@nestjs/serve-static": "^5.0.1", "@nestjs/swagger": "^11.0.3", "@nestjs/websockets": "^11.0.7", + "@octokit/core": "^6.1.3", "@types/bcrypt": "^5.0.2", "axios": "^1.7.9", "bcrypt": "^5.1.1", + "bitbucket": "^2.12.0", + "cross-fetch": "^4.1.0", "dotenv": "^16.4.7", + "git-url-parse": "^16.0.0", + "gitea-js": "^1.23.0", "passport": "^0.7.0", "passport-github2": "^0.1.12", "passport-local": "^1.0.0", diff --git a/server-refactored-v3/src/repo/git/bitbucket.ts b/server-refactored-v3/src/repo/git/bitbucket.ts new file mode 100644 index 00000000..74ff0856 --- /dev/null +++ b/server-refactored-v3/src/repo/git/bitbucket.ts @@ -0,0 +1,375 @@ +import debug from 'debug'; +import * as crypto from "crypto" +import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import { Repo } from './repo'; +import gitUrlParse = require("git-url-parse"); +debug('app:kubero:bitbucket:api') + +//const { Octokit } = require("@octokit/core"); +import { Bitbucket, APIClient } from "bitbucket" +import { RequestError } from '@octokit/types'; + +export class BitbucketApi extends Repo { + private bitbucket: APIClient; + + constructor(username: string, appPassword: string) { + super("bitbucket"); + const clientOptions = { + auth: { + username: username, + password: appPassword + }, + } + + if (process.env.BITBUCKET_USERNAME && process.env.BITBUCKET_APP_PASSWORD) { + this.bitbucket = new Bitbucket(clientOptions) + } else { + this.bitbucket = new Bitbucket() + console.log("☑️ Feature: BitBucket disabled: No BITBUCKET_USERNAME or BITBUCKET_APP_PASSWORD set") + } + } + + protected async getRepository(gitrepo: string): Promise { + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + } + } + + let parsed = gitUrlParse(gitrepo) + let repo = parsed.name + let owner = parsed.owner + + console.log(owner, repo); + try { + // https://bitbucketjs.netlify.app/#api-repositories-repositories_get + let res = await this.bitbucket.repositories.get({ + repo_slug: repo, + workspace: owner + }) + console.log(res.data); + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.uuid, + node_id: res.data.full_name as string, + name: res.data.slug as string, + description: res.data.description, + owner: res.data.owner?.nickname as string, + private : res.data.is_private, + ssh_url: res.data.links?.clone?.find((c: any) => c.name === 'ssh')?.href as string, + clone_url: res.data.links?.clone?.find((c: any) => c.name === 'https')?.href as string, + language: res.data.language, + homepage: res.data.website as string, + admin: true, // assumed since we ar loading only owned repos + push: true, // assumed since we ar loading only owned repos + //visibility: res.data.visibility, + default_branch: res.data.mainbranch?.name as string, + } + } + + } catch (e) { + let res = e as RequestError; + debug.log("Repository not found: "+ gitrepo); + ret = { + status: res.status, + statusText: 'not found', + data: { + owner: owner, + name: repo, + admin: false, + push: false, + } + } + } + return ret; + } + + public async getRepositories() { + let res = await this.bitbucket.request('GET /user/repos', {}) + return res.data; + } + + protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { + + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + } + } + + + let webhooksList = await this.bitbucket.repositories.listWebhooks({ + repo_slug: repo, + workspace: owner + }) + + let webhook = webhooksList.data.values?.find((w: any) => w.url === url); + if (webhook == undefined) { + try { + let res = await this.bitbucket.repositories.createWebhook({ + repo_slug: repo, + workspace: owner, + _body: { + description: "Kubero webhook", + url: url, + active: true, + //skip_cert_verification: false, + events: ["pullrequest:created", "repo:push"] + } + }) + ret = { + status: 201, + statusText: 'created', + data: { + id: res.data.uuid as string, + active: res.data.active as boolean, + created_at: res.data.created_at as string, + url: res.data.url as string, + insecure: !res.data.skip_cert_verification as boolean, + events: res.data.events as string[], + } + } + } catch (e) { + console.log(e) + } + } else { + console.log("Webhook already exists") + console.log(webhook) + + ret = { + status: 422, + statusText: 'created', + data: { + id: webhook.uuid as string, + active: webhook.active as boolean, + created_at: webhook.created_at as string, + url: webhook.url as string, + insecure: !webhook.skip_cert_verification as boolean, + events: webhook.events as string[], + } + } + + } + + return ret; + } + + protected async addDeployKey(owner: string, repo: string): Promise { + + const keyPair = this.createDeployKeyPair(); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: "bot@kubero", + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + + try { + // https://bitbucketjs.netlify.app/#api-repositories-repositories_createDeployKey + let res = await this.bitbucket.repositories.createDeployKey({ + label: "bot@kubero", + key: keyPair.pubKey, + repo_slug: repo, + workspace: owner + }); + + console.log(res); + + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id as number, + title: res.data.label as string, + verified: true, + created_at: res.data.created_on as string, + url: '', + read_only: false, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + } catch (e) { + let res = e as RequestError; + debug.log("Error adding deploy key: "+ res); + } + + return ret + } + + public getWebhook(event: string, delivery: string, body: any): IWebhook | boolean { + + // use github and gitea naming for the event + let github_event = event; + if (event === 'repo:push') { + github_event = 'push'; + } else if (event === 'pullrequest:created') { + github_event = 'pull_request'; + } else { + debug.log('ERROR: untranslated Bitbucket event: '+event); + return false; + } + + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.ref != undefined) { + let ref = body.ref + let refs = ref.split('/') + branch = refs[refs.length - 1] + ssh_url = body.repository.ssh_url + } else if (body.pull_request != undefined) { + action = body.action, + branch = body.pull_request.head.ref + ssh_url = body.pull_request.head.repo.ssh_url + } else { + ssh_url = body.repository.ssh_url + } + + try { + let webhook: IWebhook = { + repoprovider: 'bitbucket', + action: action, + event: github_event, + delivery: delivery, + body: body, + branch: branch, + verified: true, // bitbucket does not support verification with signatures :( + repo: { + ssh_url: ssh_url, + } + } + + return webhook; + } catch (error) { + debug.log(error) + return false; + } + } + + public async listRepos(): Promise { + let ret: string[] = []; + try { + // https://bitbucketjs.netlify.app/#api-repositories-repositories_listGlobal + const repos = await this.bitbucket.repositories.listGlobal({ role: 'member' }) + + if (repos.data.values != undefined) { + for (let repo of repos.data.values) { + if (repo.links != undefined && repo.links.clone != undefined) { + ret.push(repo.links.clone[1].href as string); + } + } + } + + } catch (error) { + debug.log(error) + } + return ret; + } + + public async getBranches(gitrepo: string): Promise { + //https://bitbucketjs.netlify.app/#api-repositories-repositories_listBranches + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches = await this.bitbucket.repositories.listBranches({ + repo_slug: repo, + workspace: owner, + sort: '-name' + }) + if (branches.data.values != undefined) { + return branches.data.values.map((branch: any) => branch.name); + } + } catch (error) { + debug.log(error) + } + + return []; + + } + + + + public async getReferences(gitrepo: string): Promise{ + + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches = await this.bitbucket.repositories.listBranches({ + repo_slug: repo, + workspace: owner, + sort: '-name' + }) + if (branches.data.values != undefined) { + ret = branches.data.values.map((branch: any) => branch.name); + } + } catch (error) { + debug.log(error) + } + + try { + const tags = await this.bitbucket.repositories.listTags({ + repo_slug: repo, + workspace: owner, + sort: '-name' + }) + if (tags.data.values != undefined) { + ret = ret.concat(tags.data.values.map((tag: any) => tag.name)); + } + } catch (error) { + debug.log(error) + } + + try { + const commits = await this.bitbucket.repositories.listCommits({ + repo_slug: repo, + workspace: owner, + sort: '-date' + }) + if (commits.data.values != undefined) { + ret = ret.concat(commits.data.values.map((commit: any) => commit.hash)); + } + } catch (error) { + debug.log(error) + } + + return ret; + + } + + public async getPullrequests(gitrepo: string): Promise{ + + let ret: IPullrequest[] = []; + + + return ret; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/repo/git/gitea.ts b/server-refactored-v3/src/repo/git/gitea.ts new file mode 100644 index 00000000..9c3e6a23 --- /dev/null +++ b/server-refactored-v3/src/repo/git/gitea.ts @@ -0,0 +1,352 @@ +import debug from 'debug'; +import * as crypto from "crypto" +import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import { Repo } from './repo'; +import gitUrlParse = require("git-url-parse"); +debug('app:kubero:gitea:api') + +//https://www.npmjs.com/package/gitea-js +import { giteaApi } from "gitea-js" +import { fetch as fetchGitea } from 'cross-fetch'; + +export class GiteaApi extends Repo { + private gitea: any; + + constructor(baseURL: string, token: string) { + super("gitea"); + this.gitea = giteaApi(baseURL, { + token: token, + customFetch: fetchGitea, + }); + } + + protected async getRepository(gitrepo: string): Promise { + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + } + } + + let parsed = gitUrlParse(gitrepo) + let repo = parsed.name + let owner = parsed.owner + + let res = await this.gitea.repos.repoGet(owner, repo) + .catch((error: any) => { + console.log(error) + return ret; + }) + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.id, + node_id: res.data.node_id, + name: res.data.name, + description: res.data.description, + owner: res.data.owner.login, + private : res.data.private, + ssh_url: res.data.ssh_url, + language: res.data.language, + homepage: res.data.homepage, + admin: res.data.permissions.admin, + push: res.data.permissions.push, + visibility: res.data.visibility, + default_branch: res.data.default_branch, + } + } + return ret; + + } + + protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { + + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + } + } + + //https://try.gitea.io/api/swagger#/repository/repoListHooks + const webhooksList = await this.gitea.repos.repoListHooks(owner, repo) + .catch((error: any) => { + console.log(error) + return ret; + }) + + // try to find the webhook + for (let webhook of webhooksList.data) { + if (webhook.config.url === url && + webhook.config.content_type === 'json' && + webhook.active === true) { + ret = { + status: 422, + statusText: 'found', + data: webhook, + } + return ret; + } + } + //console.log(webhooksList) + + // create the webhook since it does not exist + try { + + //https://try.gitea.io/api/swagger#/repository/repoCreateHook + let res = await this.gitea.repos.repoCreateHook(owner, repo, { + active: true, + config: { + url: url, + content_type: "json", + secret: secret, + insecure_ssl: '0' + }, + events: [ + "push", + "pull_request" + ], + type: "gitea" + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + active: res.data.active, + created_at: res.data.created_at, + url: res.data.url, + insecure: res.data.config.insecure_ssl, + events: res.data.events, + } + } + } catch (e) { + console.log(e) + } + return ret; + } + + + protected async addDeployKey(owner: string, repo: string): Promise { + + const keyPair = this.createDeployKeyPair(); + + const title: string = "bot@kubero."+crypto.randomBytes(4).toString('hex'); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: title, + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + + try { + //https://try.gitea.io/api/swagger#/repository/repoCreateKey + let res = await this.gitea.repos.repoCreateKey(owner, repo, { + title: title, + key: keyPair.pubKey, + read_only: true + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + title: res.data.title, + verified: res.data.verified, + created_at: res.data.created_at, + url: res.data.url, + read_only: res.data.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + } catch (e) { + console.log(e) + } + + return ret + } + + public getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean { + //https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks + let secret = process.env.KUBERO_WEBHOOK_SECRET as string; + let hash = 'sha256='+crypto.createHmac('sha256', secret).update(JSON.stringify(body, null, ' ')).digest('hex') + + let verified = false; + if (hash === signature) { + debug.debug('Gitea webhook signature is valid for event: '+delivery); + verified = true; + } else { + debug.log('ERROR: invalid signature for event: '+delivery); + debug.log('Hash: '+hash); + debug.log('Signature: '+signature); + verified = false; + return false; + } + + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.pull_request == undefined) { + let ref = body.ref + let refs = ref.split('/') + branch = refs[refs.length - 1] + ssh_url = body.repository.ssh_url + } else if (body.pull_request != undefined) { + action = body.action, + branch = body.pull_request.head.ref + ssh_url = body.pull_request.head.repo.ssh_url + } else { + ssh_url = body.repository.ssh_url + } + + try { + let webhook: IWebhook = { + repoprovider: 'gitea', + action: action, + event: event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + } + } + + return webhook; + } catch (error) { + console.log(error) + return false; + } + } + + public async listRepos(): Promise { + let ret: string[] = []; + try { + const repos = await this.gitea.user.userCurrentListRepos() + for (let repo of repos.data) { + ret.push(repo.ssh_url) + } + } catch (error) { + console.log(error) + } + return ret; + } + + public async getBranches(gitrepo: string): Promise{ + // https://try.gitea.io/api/swagger#/repository/repoListBranches + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo) + for (let branch of branches.data) { + ret.push(branch.name) + } + } catch (error) { + console.log(error) + } + + return ret; + } + + public async getReferences(gitrepo: string): Promise{ + + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo) + for (let branch of branches.data) { + ret.push(branch.name) + } + } catch (error) { + debug.log(error) + } + + try { + const tags = await this.gitea.repos.repoListTags(owner, repo) + for (let tag of tags.data) { + ret.push(tag.name) + } + } catch (error) { + debug.log(error) + } + + try { + const commits = await this.gitea.repos.repoListCommits(owner, repo) + for (let commit of commits.data) { + ret.push(commit.sha) + } + } catch (error) { + debug.log(error) + } + + return ret; + + } + + public async getPullrequests(gitrepo: string): Promise{ + + let ret: IPullrequest[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const pulls = await this.gitea.repos.repoListPullRequests(owner, repo, { + state: "open", + sort: "recentupdate"}) + for (let pr of pulls.data) { + const p: IPullrequest = { + html_url: pr.url, + number: pr.number, + title: pr.title, + state: pr.state, + //draft: pr.draft, + user: { + login: pr.user.login, + avatar_url: pr.user.avatar_url, + }, + created_at: pr.created_at, + updated_at: pr.updated_at, + closed_at: pr.closed_at, + merged_at: pr.merged_at, + //locked: pr.locked, + branch: pr.head.ref, + ssh_url: pr.head.repo.ssh_url, + } + ret.push(p) + } + + } catch (error) { + debug.log(error) + } + + return ret; + } +} diff --git a/server-refactored-v3/src/repo/git/github.ts b/server-refactored-v3/src/repo/git/github.ts new file mode 100644 index 00000000..e9e68ec1 --- /dev/null +++ b/server-refactored-v3/src/repo/git/github.ts @@ -0,0 +1,401 @@ +import debug from 'debug'; +import * as crypto from "crypto" +import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import { Repo } from './repo'; +import gitUrlParse = require("git-url-parse"); +debug('app:kubero:github:api') + +//const { Octokit } = require("@octokit/core"); +import { Octokit } from "@octokit/core" +import { RequestError } from '@octokit/types'; + +export class GithubApi extends Repo { + private octokit: any; + + constructor(token: string) { + super("github"); + this.octokit = new Octokit({ + auth: token + }); + } + + protected async getRepository(gitrepo: string): Promise { + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + } + } + + let parsed = gitUrlParse(gitrepo) + let repo = parsed.name + let owner = parsed.owner + + try { + let res = await this.octokit.request('GET /repos/{owner}/{repo}', { + owner: owner, + repo: repo, + }); + //console.log(res.data); + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.id, + node_id: res.data.node_id, + name: res.data.name, + description: res.data.description, + owner: res.data.owner.login, + private : res.data.private, + ssh_url: res.data.ssh_url, + clone_url: res.data.clone_url, + language: res.data.language, + homepage: res.data.homepage, + admin: res.data.permissions.admin, + push: res.data.permissions.push, + visibility: res.data.visibility, + default_branch: res.data.default_branch, + } + } + } catch (e) { + let res = e as RequestError; + debug.log("Repository not found: "+ gitrepo); + ret = { + status: res.status, + statusText: 'not found', + data: { + owner: owner, + name: repo, + admin: false, + push: false, + } + } + } + return ret; + } + + public async getRepositories() { + let res = await this.octokit.request('GET /user/repos', {}) + return res.data; + } + +/* + + public async getRepositoryCommits(owner: string, repo: string, branch: string) { + return await this.octokit.git.listCommits({ + owner: owner, + repo: repo, + sha: branch + }); + } +*/ + protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { + + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + } + } + + try { + let res = await this.octokit.request('POST /repos/{owner}/{repo}/hooks', { + owner: owner, + repo: repo, + active: true, + config: { + url: url, + content_type: "json", + secret: secret, + insecure_ssl: '0' + }, + events: [ + "push", + "pull_request" + ] + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + active: res.data.active, + created_at: res.data.created_at, + url: res.data.url, + insecure: res.data.config.insecure_ssl, + events: res.data.events, + } + } + } catch (e) { + let res = e as RequestError; + if (res.status === 422) { + let existingWebhooksRes = await this.octokit.request('GET /repos/{owner}/{repo}/hooks', { + owner: owner, + repo: repo, + }) + for (let webhook of existingWebhooksRes.data) { + if (webhook.config.url === url) { + debug.log("Webhook already exists"); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: webhook.id, + active: webhook.active, + created_at: webhook.created_at, + url: webhook.config.url, + insecure: webhook.config.insecure_ssl, + events: webhook.events, + } + } + } + } + } + } + + return ret; + } + + protected async addDeployKey(owner: string, repo: string): Promise { + + const keyPair = this.createDeployKeyPair(); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: "bot@kubero", + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + + try { + let res = await this.octokit.request('POST /repos/{owner}/{repo}/keys', { + owner: owner, + repo: repo, + title: "bot@kubero", + key: keyPair.pubKey, + read_only: true + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + title: res.data.title, + verified: res.data.verified, + created_at: res.data.created_at, + url: res.data.url, + read_only: res.data.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + } catch (e) { + let res = e as RequestError; + debug.log("Error adding deploy key: "+ res); + } + + return ret + } + + public getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean { + //https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks + let secret = process.env.KUBERO_WEBHOOK_SECRET as string; + let hash = 'sha256='+crypto.createHmac('sha256', secret).update(JSON.stringify(body)).digest('hex') + + let verified = false; + if (hash === signature) { + debug.debug('Github webhook signature is valid for event: '+delivery); + verified = true; + } else { + debug.log('ERROR: invalid signature for event: '+delivery); + debug.log('Hash: '+hash); + debug.log('Signature: '+signature); + verified = false; + return false; + } + + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.ref != undefined) { + let ref = body.ref + let refs = ref.split('/') + branch = refs[refs.length - 1] + ssh_url = body.repository.ssh_url + } else if (body.pull_request != undefined) { + action = body.action, + branch = body.pull_request.head.ref + ssh_url = body.pull_request.head.repo.ssh_url + } else { + ssh_url = body.repository.ssh_url + } + + try { + let webhook: IWebhook = { + repoprovider: 'github', + action: action, + event: event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + } + } + + return webhook; + } catch (error) { + debug.log(error) + return false; + } + } + + public async listRepos(): Promise { + let ret: string[] = []; + try { + const repos = await this.octokit.request('GET /user/repos', { + visibility: 'all', + per_page: 100, + sort: 'updated' + }) + for (let repo of repos.data) { + ret.push(repo.ssh_url) + } + } catch (error) { + debug.log(error) + } + return ret; + } + + public async getBranches(gitrepo: string): Promise{ + + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches = await this.octokit.request('GET /repos/{owner}/{repo}/branches', { + owner: owner, + repo: repo, + }) + for (let branch of branches.data) { + ret.push(branch.name) + } + } catch (error) { + debug.log(error) + } + + return ret; + + } + + public async getReferences(gitrepo: string): Promise{ + + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches = await this.octokit.request('GET /repos/{owner}/{repo}/branches', { + owner: owner, + repo: repo, + }) + for (let branch of branches.data) { + ret.push(branch.name) + } + } catch (error) { + debug.log(error) + } + + try { + const tags = await this.octokit.request('GET /repos/{owner}/{repo}/tags', { + owner: owner, + repo: repo, + }) + for (let tag of tags.data) { + ret.push(tag.name) + } + } catch (error) { + debug.log(error) + } + + try { + const commits = await this.octokit.request('GET /repos/{owner}/{repo}/commits', { + owner: owner, + repo: repo, + }) + for (let commit of commits.data) { + ret.push(commit.sha) + } + } catch (error) { + debug.log(error) + } + + return ret; + + } + + public async getPullrequests(gitrepo: string): Promise{ + + let ret: IPullrequest[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const pulls = await this.octokit.request('GET /repos/{owner}/{repo}/pulls', { + owner: owner, + repo: repo, + state: 'open' + }) + //console.log(pulls) + for (let pr of pulls.data) { + const p: IPullrequest = { + html_url: pr.html_url, + number: pr.number, + title: pr.title, + state: pr.state, + draft: pr.draft, + user: { + login: pr.user.login, + avatar_url: pr.user.avatar_url, + }, + created_at: pr.created_at, + updated_at: pr.updated_at, + closed_at: pr.closed_at, + merged_at: pr.merged_at, + locked: pr.locked, + branch: pr.head.ref, + ssh_url: pr.head.repo.ssh_url, + } + ret.push(p) + } + } catch (error) { + debug.log(error) + } + + return ret; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/repo/git/gitlab.ts b/server-refactored-v3/src/repo/git/gitlab.ts new file mode 100644 index 00000000..e09e49e5 --- /dev/null +++ b/server-refactored-v3/src/repo/git/gitlab.ts @@ -0,0 +1,378 @@ +// https://www.nerd.vision/post/nerdvision-gitlab-js-an-easier-way-to-access-the-gitlab-api-in-javascript +// https://www.npmjs.com/package/@nerdvision/gitlab-js +import debug from 'debug'; +import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import { Repo } from './repo'; +import {Client as GitlabClient} from '@nerdvision/gitlab-js'; +import {Options} from 'got'; +import gitUrlParse = require("git-url-parse"); + + +export class GitlabApi extends Repo { + private gitlab: GitlabClient; + private opt = { + headers: { + 'Content-Type': 'application/json', + }, + } as Options; + + constructor(baseURL: string, token: string) { + super("gitlab"); + const host = baseURL || 'https://gitlab.com'; + + if (token == undefined) { + console.log('☑️ Feature: Gitlab not configured (no token)'); + } else { + console.log('✅ Feature: Gitlab configured: '+host); + } + + this.gitlab = new GitlabClient({ + token: token, + host: host, + }); + } + + protected async getRepository(gitrepo: string): Promise { + //https://docs.gitlab.com/ee/api/projects.html + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + } + } + + let parsed = gitUrlParse(gitrepo) + let repo = parsed.name + let owner = parsed.owner + + let res: any = await this.gitlab.get(`projects/${owner}%2F${repo}`) + .catch((error: any) => { + console.log(error) + return ret; + }) + //console.log(res) + + res.private = false; + if (res.visibility === 'private') { + res.private = true; + } + + // TODO: this is a workaround since the information is not available + res.permissions.admin = true; + res.permissions.push = true; + + ret = { + status: 200, + statusText: 'found', + data: { + id: res.id, + node_id: res.path_with_namespace, + name: res.path, + description: res.description, + owner: res.namespace.path, + private : res.private, + ssh_url: res.ssh_url_to_repo, + language: res.language, + homepage: res.namespace.web_url, + admin: res.permissions.admin, + push: res.permissions.push, + visibility: res.visibility, + default_branch: res.default_branch, + } + } + return ret; + + } + + + protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { + // https://docs.gitlab.com/ee/api/projects.html#list-project-hooks + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + } + } + + const webhooksList: any = await this.gitlab.get(`projects/${owner}%2F${repo}/hooks`) + .catch((error: any) => { + console.log(error) + return ret; + }) + // try to find the webhook + for (let webhook of webhooksList) { + if (webhook.url === url && + webhook.disabled_until === null) { + ret = { + status: 422, + statusText: 'found', + data: { + id: webhook.id, + active: true, + created_at: webhook.created_at, + url: webhook.url, + insecure: false, //TODO use the inverted enable_ssl_verification field + events: ["pull_request", "push"], + } + } + return ret; + } + } + + // create the webhook since it does not exist + try { + let res: any = await this.gitlab.post(`projects/${owner}%2F${repo}/hooks`, JSON.stringify({ + url: url, + token: secret, + merge_requests_events: true, + push_events: true, + }), + undefined, + this.opt, + ); + + ret = { + status: 201, + statusText: 'created', + data: { + id: res.id, + active: res.active, + created_at: res.created_at, + url: res.url, + insecure: false, + events: ["pull_request", "push"], + } + } + } catch (e) { + console.log("Failed to create Webhook") + console.log(e) + } + return ret; + } + + async addDeployKey(owner: string, repo: string): Promise { + + const keyPair = this.createDeployKeyPair(); + + const title: string = "bot@kubero."+Date.now(); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: title, + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + try { + // https://docs.gitlab.com/ee/api/deploy_keys.html#add-deploy-key + let res:any = await this.gitlab.post(`projects/${owner}%2F${repo}/deploy_keys`, JSON.stringify({ + title: title, + key: keyPair.pubKey, + can_push: false + }), + undefined, + this.opt, + ); + + console.log(res) + + ret = { + status: 201, + statusText: 'created', + data: { + id: res.id, + title: res.title, + verified: res.verified, + created_at: res.created_at, + url: res.url, + read_only: res.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + } catch (e) { + console.log(e) + } + + return ret + } + + public getWebhook(event: string, delivery: string, token: string, body: any): IWebhook | boolean { + let secret = process.env.KUBERO_WEBHOOK_SECRET as string; + + let verified = false; + if (secret === token) { + debug.debug('Gitlab webhook signature is valid for event: '+delivery); + verified = true; + } else { + debug.log('ERROR: invalid token/secret for event: '+delivery); + debug.log('Secret: '+secret); + debug.log('Token : '+token); + verified = false; + return false; + } + + // use github and gitea naming for the event + let github_event = event; + if (event === 'Push Hook') { + github_event = 'push'; + } else if (event === 'Merge Request Hook') { + github_event = 'pull_request'; + } else { + debug.log('ERROR: unknown event: '+event); + return false; + } + + + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.ref != undefined) { + let ref = body.ref + let refs = ref.split('/') + branch = refs[refs.length - 1] + ssh_url = body.project.git_ssh_url + } else if (body.pull_request != undefined) { + action = body.action, + branch = body.pull_request.head.ref + ssh_url = body.pull_request.head.repo.ssh_url + } else { + ssh_url = body.project.git_ssh_url + } + + try { + let webhook: IWebhook = { + repoprovider: 'gitlab', + action: action, + event: github_event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + } + } + + return webhook; + } catch (error) { + debug.log(error) + return false; + } + } + + public async listRepos(): Promise { + let ret: string[] = []; + const repos:any = await this.gitlab.get('projects', { membership: true }) + .catch((error: any) => { + console.log(error) + return ret; + }) + + for (let repo of repos) { + ret.push(repo.ssh_url_to_repo) + } + return ret; + } + + public async getBranches(gitrepo: string): Promise{ + // https://docs.gitlab.com/ee/api/branches.html#list-repository-branches + // not implemented yet + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/branches`) + .catch((error: any) => { + console.log(error) + return ret; + }) + + for (let branch of branches) { + ret.push(branch.name) + } + } catch (error) { + console.log(error) + } + + + return ret; + } + + public async getReferences(gitrepo: string): Promise{ + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/branches`) + .catch((error: any) => { + console.log(error) + return ret; + }) + + for (let branch of branches) { + ret.push(branch.name) + } + } catch (error) { + console.log(error) + } + + try { + const tags:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/tags`) + .catch((error: any) => { + console.log(error) + return ret; + }) + + for (let tag of tags) { + ret.push(tag.name) + } + } catch (error) { + console.log(error) + } + + try { + const commits:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/commits`) + .catch((error: any) => { + console.log(error) + return ret; + }) + + for (let commit of commits) { + ret.push(commit.id) + } + } catch (error) { + console.log(error) + } + + return ret; + } + + public async getPullrequests(gitrepo: string): Promise{ + + let ret: IPullrequest[] = []; + + + return ret; + } + +} \ No newline at end of file diff --git a/server-refactored-v3/src/repo/git/gogs.ts b/server-refactored-v3/src/repo/git/gogs.ts new file mode 100644 index 00000000..433d4c4f --- /dev/null +++ b/server-refactored-v3/src/repo/git/gogs.ts @@ -0,0 +1,331 @@ +import debug from 'debug'; +import * as crypto from "crypto" +import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import { Repo } from './repo'; +import gitUrlParse = require("git-url-parse"); +debug('app:kubero:gogs:api') + +//https://www.npmjs.com/package/gitea-js +import { giteaApi, Api } from "gitea-js" +import { fetch as fetchGitea } from 'cross-fetch'; + +export class GogsApi extends Repo { + private gitea: any; + + constructor(baseURL: string, token: string) { + super("gogs"); + this.gitea = giteaApi(baseURL, { + token: token, + customFetch: fetchGitea, + }); + } + + protected async getRepository(gitrepo: string): Promise { + const GitUrlParse = require("git-url-parse"); + + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + } + } + + let parsed = gitUrlParse(gitrepo) + let repo = parsed.name + let owner = parsed.owner + + if ( owner == undefined ){ + debug.log("git owner extraction failed"); + throw new Error("git owner extraction failed"); + } + if ( repo == undefined ){ + debug.log("git owner extraction failed"); + throw new Error("git repo extraction failed"); + } + + let res = await this.gitea.repos.repoGet(owner, repo) + .catch((error: any) => { + console.log(error) + return ret; + }) + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.id, + node_id: res.data.node_id, + name: res.data.name, + description: res.data.description, + owner: res.data.owner.login, + private : res.data.private, + ssh_url: res.data.ssh_url, + language: res.data.language, + homepage: res.data.homepage, + admin: res.data.permissions.admin, + push: res.data.permissions.push, + visibility: res.data.visibility, + default_branch: res.data.default_branch, + } + } + return ret; + + } + + protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { + + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + } + } + + //https://try.gitea.io/api/swagger#/repository/repoListHooks + const webhooksList = await this.gitea.repos.repoListHooks(owner, repo) + .catch((error: any) => { + console.log(error) + return ret; + }) + + // try to find the webhook + for (let webhook of webhooksList.data) { + if (webhook.config.url === url && + webhook.config.content_type === 'json' && + webhook.active === true) { + ret = { + status: 422, + statusText: 'found', + data: webhook, + } + return ret; + } + } + //console.log(webhooksList) + + // create the webhook since it does not exist + try { + + //https://try.gitea.io/api/swagger#/repository/repoCreateHook + let res = await this.gitea.repos.repoCreateHook(owner, repo, { + active: true, + config: { + url: url, + content_type: "json", + secret: secret, + insecure_ssl: '0' + }, + events: [ + "push", + "pull_request" + ], + type: "gogs" + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + active: res.data.active, + created_at: res.data.created_at, + url: res.data.url, + insecure: res.data.config.insecure_ssl, + events: res.data.events, + } + } + } catch (e) { + console.log(e) + } + return ret; + } + + + protected async addDeployKey(owner: string, repo: string): Promise { + + const keyPair = this.createDeployKeyPair(); + + const title: string = "bot@kubero."+crypto.randomBytes(4).toString('hex'); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: title, + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + try { + //https://try.gitea.io/api/swagger#/repository/repoCreateKey + let res = await this.gitea.repos.repoCreateKey(owner, repo, { + title: title, + key: keyPair.pubKey, + read_only: true + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + title: res.data.title, + verified: res.data.verified, + created_at: res.data.created_at, + url: res.data.url, + read_only: res.data.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64 + } + } + } catch (e) { + console.log(e) + } + + return ret + } + + public getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean { + let secret = process.env.KUBERO_WEBHOOK_SECRET as string; + let hash = crypto.createHmac('sha256', secret).update(JSON.stringify(body, null, ' ')).digest('hex') + + let verified = false; + if (hash === signature) { + debug.debug('Gogs webhook signature is valid for event: '+delivery); + verified = true; + } else { + debug.log('ERROR: invalid signature for event: '+delivery); + debug.log('Hash: '+hash); + debug.log('Signature: '+signature); + verified = false; + return false; + } + + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.pull_request == undefined) { + let ref = body.ref + let refs = ref.split('/') + branch = refs[refs.length - 1] + ssh_url = body.repository.ssh_url + } else if (body.pull_request != undefined) { + action = body.action, + branch = body.pull_request.head.ref + ssh_url = body.pull_request.head.repo.ssh_url + } else { + ssh_url = body.repository.ssh_url + } + + try { + let webhook: IWebhook = { + repoprovider: 'gogs', + action: action, + event: event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + } + } + + return webhook; + } catch (error) { + console.log(error) + return false; + } + } + + public async listRepos(): Promise { + let ret: string[] = []; + try { + const repos = await this.gitea.user.userCurrentListRepos() + for (let repo of repos.data) { + ret.push(repo.ssh_url) + } + } catch (error) { + console.log(error) + } + return ret; + } + + public async getBranches(gitrepo: string): Promise{ + // https://try.gitea.io/api/swagger#/repository/repoListBranches + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo) + for (let branch of branches.data) { + ret.push(branch.name) + } + } catch (error) { + console.log(error) + } + + return ret; + } + + public async getReferences(gitrepo: string): Promise{ + + let ret: string[] = []; + + let {repo, owner} = this.parseRepo(gitrepo) + + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo) + for (let branch of branches.data) { + ret.push(branch.name) + } + } catch (error) { + debug.log(error) + } + + try { + const tags = await this.gitea.repos.repoListTags(owner, repo) + for (let tag of tags.data) { + ret.push(tag.name) + } + } catch (error) { + debug.log(error) + } + + try { + const commits = await this.gitea.repos.repoListCommits(owner, repo) + for (let commit of commits.data) { + ret.push(commit.sha) + } + } catch (error) { + debug.log(error) + } + + return ret; + + } + + public async getPullrequests(gitrepo: string): Promise{ + + let ret: IPullrequest[] = []; + + + return ret; + } +} diff --git a/server-refactored-v3/src/repo/git/repo.test.ts b/server-refactored-v3/src/repo/git/repo.test.ts new file mode 100644 index 00000000..012d42db --- /dev/null +++ b/server-refactored-v3/src/repo/git/repo.test.ts @@ -0,0 +1,40 @@ +import { GithubApi } from './github'; +import { GogsApi } from './gogs'; +import { GitlabApi } from './gitlab'; +import { BitbucketApi } from './bitbucket'; +import { GiteaApi } from './gitea'; + +describe('GithubApi', () => { + it('should load config', () => { + const github = new GithubApi("token"); + expect(github).toBeTruthy(); + }); +}); + +describe('GogsApi', () => { + it('should load config', () => { + const gogs = new GogsApi("http://localhost:3000", "token"); + expect(gogs).toBeTruthy(); + }); +}); + +describe('GitlabApi', () => { + it('should load config', () => { + const gitlab = new GitlabApi("https://gitlab.com", "token"); + expect(gitlab).toBeTruthy(); + }); +}); + +describe('GiteaApi', () => { + it('should load config', () => { + const gitea = new GiteaApi("https://codeberg.org", "token"); + expect(gitea).toBeTruthy(); + }); +}); + +describe('Bitbucket', () => { + it('should load config', () => { + const bitbucket = new BitbucketApi("username", "password"); + expect(bitbucket).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/server-refactored-v3/src/repo/git/repo.ts b/server-refactored-v3/src/repo/git/repo.ts new file mode 100644 index 00000000..bbcbcc06 --- /dev/null +++ b/server-refactored-v3/src/repo/git/repo.ts @@ -0,0 +1,136 @@ +import debug from 'debug'; +import * as crypto from "crypto" +import sshpk from 'sshpk'; +import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import { IDeployKeyPair} from '../repo.interface'; +debug('app:kubero:git:repo') + +export abstract class Repo { + + protected repoProvider: string; + + constructor(repoProvider: string) { + this.repoProvider = repoProvider; + } + + protected createDeployKeyPair(): IDeployKeyPair{ + debug.debug('createDeployKeyPair'); + + const keyPair = crypto.generateKeyPairSync('ed25519', { + //modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + //cipher: 'aes-256-cbc', + //passphrase: '' + } + }); + debug.debug(JSON.stringify(keyPair)); + + const pubKeySsh = sshpk.parseKey(keyPair.publicKey, 'pem'); + const pubKeySshString = pubKeySsh.toString('ssh'); + const fingerprint = pubKeySsh.fingerprint('sha256').toString('hex'); + console.debug(pubKeySshString); + + const privKeySsh = sshpk.parsePrivateKey(keyPair.privateKey, 'pem'); + const privKeySshString = privKeySsh.toString('ssh'); + console.debug(privKeySshString); + + return { + fingerprint: fingerprint, + pubKey: pubKeySshString, + pubKeyBase64: Buffer.from(pubKeySshString).toString('base64'), + privKey: privKeySshString, + privKeyBase64: Buffer.from(privKeySshString).toString('base64') + }; + } + + public async connectRepo(gitrepo: string): Promise<{keys: IDeploykeyR | undefined, repository: IRepository, webhook: IWebhookR | undefined}> { + debug.log('connectPipeline: '+gitrepo); + + if (process.env.KUBERO_WEBHOOK_SECRET == undefined) { + debug.log("KUBERO_WEBHOOK_SECRET is not defined") + throw new Error("KUBERO_WEBHOOK_SECRET is not defined"); + } + if (process.env.KUBERO_WEBHOOK_URL == undefined) { + debug.log("KUBERO_WEBHOOK_URL is not defined") + throw new Error("KUBERO_WEBHOOK_URL is not defined"); + } + + const repository = await this.getRepository(gitrepo) + console.debug(repository); + + let keys: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: "bot@kubero", + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: '', + priv: '', + } + } + let webhook: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + } + } + if (repository.status == 200 && repository.data.admin == true) { + + webhook = await this.addWebhook( + repository.data.owner, + repository.data.name, + process.env.KUBERO_WEBHOOK_URL+'/'+this.repoProvider, + process.env.KUBERO_WEBHOOK_SECRET, + ); + + keys = await this.addDeployKey(repository.data.owner, repository.data.name); + } + + return {keys: keys, repository: repository, webhook: webhook}; + + } + + public async disconnectRepo(gitrepo: string): Promise { + debug.log('disconnectPipeline: '+gitrepo); + + const {owner, repo} = this.parseRepo(gitrepo); + + // TODO: implement remove deploy key and webhook for all providers + //this.removeDeployKey(owner, repo, 0); + //this.removeWebhook(owner, repo, 0); + + return true; + } + + protected parseRepo(gitrepo: string): {owner: string, repo: string} { + let owner = gitrepo.match(/^git@.*:(.*)\/.*$/)?.[1] as string; + let repo = gitrepo.match(/^git@.*:.*\/(.*).git$/)?.[1] as string; + return { owner: owner, repo: repo }; + } + + protected abstract addDeployKey(owner: string, repo: string): Promise + //protected abstract removeDeployKey(owner: string, repo: string, id: number): Promise + protected abstract getRepository(gitrepo: string): Promise; + protected abstract addWebhook(owner: string, repo: string, url: string, secret: string): Promise; + protected abstract getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean; + //protected abstract removeWebhook(owner: string, repo: string, id: number): Promise; + protected abstract getBranches(repo: string): Promise | undefined; + protected abstract getReferences(repo: string): Promise | undefined; + protected abstract getPullrequests(repo: string): Promise | undefined; +} \ No newline at end of file diff --git a/server-refactored-v3/src/repo/git/types.ts b/server-refactored-v3/src/repo/git/types.ts new file mode 100644 index 00000000..74ca2173 --- /dev/null +++ b/server-refactored-v3/src/repo/git/types.ts @@ -0,0 +1,80 @@ +export interface IWebhook { + repoprovider: 'gitea' | 'gitlab' | 'github' | 'bitbucket' | 'gogs' | 'onedev', + action: 'opened' | 'reopened' | 'closed' | undefined, + event: string, + delivery: string, + body: any, + branch: string, + verified: boolean, + repo: { + ssh_url: string, + } +} + +export interface IRepository { + status: number, + statusText: 'error' | 'not found' | 'found', + data: { + id?: number | string, // bitbucket uses UUID's + node_id?: string, + name: string, + description?: string, + owner: string, + private?: boolean, + ssh_url?: string, + clone_url?: string, + language?: string, + homepage?: string, + admin: boolean, + push: boolean, + visibility?: string, + default_branch?: string + } +} + +export interface IWebhookR { + status: number, + statusText: 'error' | 'created' | 'not found' | 'found', + data: { + id?: number | string, // bitbucket uses UUID's + active: boolean, + created_at: string, + url: string, + insecure: boolean, + events: string[], + } +} + +export interface IDeploykeyR { + status: number, + statusText: 'error' | 'created' | 'not found' | 'found', + data: { + id?: number, + title: string, + verified: boolean, + created_at: string, + url: string, + read_only: boolean, + pub: string, + priv: string + } +} + +export interface IPullrequest { + html_url: string, + number: number, + title: string, + state: string, + user: { + login: string, + avatar_url: string, + }, + created_at: string, + updated_at: string, + closed_at: string, + merged_at: string, + locked?: boolean, + draft?: boolean, + branch: string, + ssh_url: string, +} diff --git a/server-refactored-v3/src/repo/repo.controller.spec.ts b/server-refactored-v3/src/repo/repo.controller.spec.ts new file mode 100644 index 00000000..db111ff5 --- /dev/null +++ b/server-refactored-v3/src/repo/repo.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RepoController } from './repo.controller'; + +describe('RepoController', () => { + let controller: RepoController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [RepoController], + }).compile(); + + controller = module.get(RepoController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/repo/repo.controller.ts b/server-refactored-v3/src/repo/repo.controller.ts new file mode 100644 index 00000000..63780e54 --- /dev/null +++ b/server-refactored-v3/src/repo/repo.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { RepoService } from './repo.service'; +import { ApiOperation } from '@nestjs/swagger'; + +@Controller({ path: 'api/repo', version: '1' }) +export class RepoController { + constructor( + private readonly repoService: RepoService, + ) {} + + @ApiOperation({ summary: 'Get a list of all available repository providers' }) + @Get('/providers') + async listRepositories() { + return this.repoService.listRepositories(); + } + + @ApiOperation({ summary: 'Get a list of all available repositories' }) + @Get('/:provider/repositories') + async listRepositoriesByProvider( + @Param('provider') provider: string, + ) { + return this.repoService.listRepositoriesByProvider(provider); + } +} diff --git a/server-refactored-v3/src/repo/repo.interface.ts b/server-refactored-v3/src/repo/repo.interface.ts new file mode 100644 index 00000000..f21ffb0c --- /dev/null +++ b/server-refactored-v3/src/repo/repo.interface.ts @@ -0,0 +1,7 @@ +export interface IDeployKeyPair { + fingerprint: string; + pubKey: string; + pubKeyBase64: string; + privKey: string; + privKeyBase64: string; +} \ No newline at end of file diff --git a/server-refactored-v3/src/repo/repo.module.ts b/server-refactored-v3/src/repo/repo.module.ts index 8cc04d64..bcbacd8c 100644 --- a/server-refactored-v3/src/repo/repo.module.ts +++ b/server-refactored-v3/src/repo/repo.module.ts @@ -1,4 +1,9 @@ import { Module } from '@nestjs/common'; +import { RepoController } from './repo.controller'; +import { RepoService } from './repo.service'; -@Module({}) +@Module({ + controllers: [RepoController], + providers: [RepoService] +}) export class RepoModule {} diff --git a/server-refactored-v3/src/repo/repo.service.spec.ts b/server-refactored-v3/src/repo/repo.service.spec.ts new file mode 100644 index 00000000..a80f23c8 --- /dev/null +++ b/server-refactored-v3/src/repo/repo.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RepoService } from './repo.service'; + +describe('RepoService', () => { + let service: RepoService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RepoService], + }).compile(); + + service = module.get(RepoService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/repo/repo.service.ts b/server-refactored-v3/src/repo/repo.service.ts new file mode 100644 index 00000000..935b2d56 --- /dev/null +++ b/server-refactored-v3/src/repo/repo.service.ts @@ -0,0 +1,219 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { GithubApi } from './git/github'; +import { BitbucketApi } from './git/bitbucket'; +import { GiteaApi } from './git/gitea'; +import { GogsApi } from './git/gogs'; +import { GitlabApi } from './git/gitlab'; +import { IPullrequest } from './git/types'; + +@Injectable() +export class RepoService { + private readonly logger = new Logger(RepoService.name); + private githubApi: GithubApi; + private giteaApi: GiteaApi; + private gogsApi: GogsApi; + private gitlabApi: GitlabApi; + private bitbucketApi: BitbucketApi; + + constructor() { + this.giteaApi = new GiteaApi(process.env.GITEA_BASEURL as string, process.env.GITEA_PERSONAL_ACCESS_TOKEN as string); + this.gogsApi = new GogsApi(process.env.GOGS_BASEURL as string, process.env.GOGS_PERSONAL_ACCESS_TOKEN as string); + this.githubApi = new GithubApi(process.env.GITHUB_PERSONAL_ACCESS_TOKEN as string); + this.gitlabApi = new GitlabApi(process.env.GITLAB_BASEURL as string, process.env.GITLAB_PERSONAL_ACCESS_TOKEN as string); + this.bitbucketApi = new BitbucketApi(process.env.BITBUCKET_USERNAME as string, process.env.BITBUCKET_APP_PASSWORD as string); + } + + public async listReferences(repoProvider: string, repoB64: string ): Promise { + //return this.git.listRepoBranches(repo, repoProvider); + let ref: Promise = new Promise((resolve, reject) => { + resolve([]); + }); + + const repo = Buffer.from(repoB64, 'base64').toString('ascii'); + + switch (repoProvider) { + case 'github': + ref = this.githubApi.getReferences(repo); + break; + case 'gitea': + ref = this.giteaApi.getReferences(repo); + break; + case 'gogs': + ref = this.gogsApi.getReferences(repo); + break; + case 'gitlab': + ref = this.gitlabApi.getReferences(repo); + break; + case 'bitbucket': + ref = this.bitbucketApi.getReferences(repo); + break; + case 'onedev': + default: + break; + } + + return ref + } + + public async listRepositoriesByProvider(repoProvider: string) { + this.logger.debug('listRepos: '+repoProvider); + + switch (repoProvider) { + case 'github': + return this.githubApi.listRepos(); + case 'gitea': + return this.giteaApi.listRepos(); + case 'gogs': + return this.gogsApi.listRepos(); + case 'gitlab': + return this.gitlabApi.listRepos(); + case 'bitbucket': + return this.bitbucketApi.listRepos(); + case 'onedev': + default: + return {'error': 'unknown repo provider'}; + } + } + + public async connectRepo(repoProvider: string, repoAddress: string) { + this.logger.debug('connectRepo: '+repoProvider+' '+repoAddress); + + switch (repoProvider) { + case 'github': + return this.githubApi.connectRepo(repoAddress); + case 'gitea': + return this.giteaApi.connectRepo(repoAddress); + case 'gogs': + return this.gogsApi.connectRepo(repoAddress); + case 'gitlab': + return this.gitlabApi.connectRepo(repoAddress); + case 'bitbucket': + return this.bitbucketApi.connectRepo(repoAddress); + case 'onedev': + default: + return {'error': 'unknown repo provider'}; + } + } + + public async disconnectRepo(repoProvider: string, repoAddress: string) { + this.logger.debug('disconnectRepo: '+repoProvider+' '+repoAddress); + + switch (repoProvider) { + case 'github': + return this.githubApi.disconnectRepo(repoAddress); + case 'gitea': + return this.giteaApi.disconnectRepo(repoAddress); + case 'gogs': + return this.gogsApi.disconnectRepo(repoAddress); + case 'gitlab': + return this.gitlabApi.disconnectRepo(repoAddress); + case 'bitbucket': + return this.bitbucketApi.disconnectRepo(repoAddress); + case 'onedev': + default: + return {'error': 'unknown repo provider'}; + } + } + + public async listRepoBranches(repoProvider: string, repoB64: string ): Promise { + //return this.git.listRepoBranches(repo, repoProvider); + let branches: Promise = new Promise((resolve, reject) => { + resolve([]); + }); + + const repo = Buffer.from(repoB64, 'base64').toString('ascii'); + + switch (repoProvider) { + case 'github': + branches = this.githubApi.getBranches(repo); + break; + case 'gitea': + branches = this.giteaApi.getBranches(repo); + break; + case 'gogs': + branches = this.gogsApi.getBranches(repo); + break; + case 'gitlab': + branches = this.gitlabApi.getBranches(repo); + break; + case 'bitbucket': + branches = this.bitbucketApi.getBranches(repo); + break; + case 'onedev': + default: + break; + } + + return branches + } + + public async listRepoPullrequests(repoProvider: string, repoB64: string ): Promise { + //return this.git.listRepoBranches(repo, repoProvider); + let pulls: Promise = new Promise((resolve, reject) => { + resolve([]); + }); + + const repo = Buffer.from(repoB64, 'base64').toString('ascii'); + + switch (repoProvider) { + case 'github': + pulls = this.githubApi.getPullrequests(repo); + break; + case 'gitea': + pulls = this.giteaApi.getPullrequests(repo); + break; + case 'gogs': + pulls = this.gogsApi.getPullrequests(repo); + break; + case 'gitlab': + pulls = this.gitlabApi.getPullrequests(repo); + break; + case 'bitbucket': + pulls = this.bitbucketApi.getPullrequests(repo); + break; + case 'onedev': + default: + break; + } + + return pulls + } + + public listRepositories() { + let repositories = { + github: false, + gitea: false, + gitlab: false, + gogs: false, + onedev: false, + bitbucket: false, + docker: true + } + + if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) { + repositories.github = true; + } + + if (process.env.GITEA_PERSONAL_ACCESS_TOKEN) { + repositories.gitea = true; + } + + if (process.env.GITLAB_PERSONAL_ACCESS_TOKEN) { + repositories.gitlab = true; + } + + if (process.env.GOGS_PERSONAL_ACCESS_TOKEN) { + repositories.gogs = true; + } + + if (process.env.ONEDEV_PERSONAL_ACCESS_TOKEN) { + repositories.onedev = true; + } + + if (process.env.BITBUCKET_USERNAME && process.env.BITBUCKET_APP_PASSWORD) { + repositories.bitbucket = true; + } + + return repositories; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts index 6acef4c4..eb51a3ad 100644 --- a/server-refactored-v3/src/settings/settings.controller.ts +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -31,4 +31,33 @@ export class SettingsController { async getTemplates() { return this.settingsService.getTemplateConfig(); } + + // TODO: Move to kubernetes module + @ApiOperation({ summary: 'Get available contexts' }) + @Get('/contexts') + async getContexts() { + return this.settingsService.getContexts(); + } + + @ApiOperation({ summary: 'Get the registry settings' }) + @Get('/registry') + async getRegistry() { + return this.settingsService.getRegistry(); + } + + @ApiOperation({ summary: 'List runpacks' }) + @Get('/runpacks') + async getRunpacks() { + return this.settingsService.getRunpacks(); + } +/* + @Get('/clusterissuer') + async getClusterIssuer() { + return this.settingsService.getClusterIssuer(); + } + @Get('/buildpacks') + async getBuildpacks() { + return this.settingsService.getBuildpacks(); + } +*/ } diff --git a/server-refactored-v3/src/settings/settings.interface.ts b/server-refactored-v3/src/settings/settings.interface.ts index b55cfa52..646c8d3d 100644 --- a/server-refactored-v3/src/settings/settings.interface.ts +++ b/server-refactored-v3/src/settings/settings.interface.ts @@ -143,4 +143,20 @@ export interface ISecurityContext { drop: string[]; add: string[]; } -} \ No newline at end of file +} + +export type IRegistry = { + account: { + hash: string + password: string + username: string + } + create: boolean + enabled: boolean + host: string + port: number + storage: string + storageClassName: any + subpath: string +} + \ No newline at end of file diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 6d9c9fc2..0a49bc54 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -1,10 +1,11 @@ import { Injectable, Logger } from '@nestjs/common'; -import { IKuberoCRD, IKuberoConfig } from './settings.interface'; +import { IKuberoCRD, IKuberoConfig, IRegistry } from './settings.interface'; import { KuberoConfig } from './kubero-config/kubero-config'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { readFileSync, writeFileSync } from 'fs'; import YAML from 'yaml' import { join } from 'path'; +import { Context } from '@kubernetes/client-node'; @Injectable() export class SettingsService { @@ -36,15 +37,20 @@ export class SettingsService { // Load settings from a file or from kubernetes async getSettings(): Promise { - if (this.checkAdminDisabled()) { return new KuberoConfig(new Object() as IKuberoConfig) } - const oo = await this.readConfig() - const configMap = new KuberoConfig(oo) + // TODO: This might fail with a local filesystem config let config: any = {} - config.settings = configMap + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + let kuberoes = await this.kubectl.getKuberoConfig(namespace) + config.settings = kuberoes.spec + /* + const kuberoconfig = await this.readConfig() + config.settings = new KuberoConfig(kuberoconfig) + */ + config["secrets"] = { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PERSONAL_ACCESS_TOKEN || '', @@ -288,11 +294,31 @@ export class SettingsService { } private async runFeatureCheck() { - //this.features.sleep = this.config.sleep.enabled; this.features.sleep = await this.checkForZeropod() } public getSleepEnabled(): boolean { return this.features.sleep } + + public getContexts(): Context[] { + return this.kubectl.getContexts() + } + + public async getRegistry(): Promise { + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + let kuberoes = await this.kubectl.getKuberoConfig(namespace) + return kuberoes.spec.registry + } + + public getRunpacks(): any[] { + return this.runningConfig.buildpacks || [] + } +/* + public async getClusterIssuer(): Promise { + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + const kuberoes = await this.kubectl.getClusterIssuer(namespace) + return kuberoes.kubero.config.clusterissuer + } +*/ } diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 8f3be3fe..befb03f2 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -1010,6 +1010,13 @@ "@napi-rs/nice-win32-ia32-msvc" "1.0.1" "@napi-rs/nice-win32-x64-msvc" "1.0.1" +"@nerdvision/gitlab-js@^1.0.0-alpha.12": + version "1.0.0-alpha.12" + resolved "https://registry.yarnpkg.com/@nerdvision/gitlab-js/-/gitlab-js-1.0.0-alpha.12.tgz#2e869b9cb8302284bc2940b3878accc8db17a040" + integrity sha512-+Svx3uo/lX+lQ2E3/wn1aOtNcTdcSfycIXwfGv7/rrk6gZ770E/jiyCYUoTSaHKS0nW0lnx7tZeXZfwaEElzBw== + dependencies: + got "^11.8.1" + "@nestjs/cli@^11.0.0": version "11.0.2" resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-11.0.2.tgz#dff9b0bda813b141c1f4c19fdc9a0138d66eeacc" @@ -1159,6 +1166,71 @@ dependencies: consola "^3.2.3" +"@octokit/auth-token@^5.0.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.2.tgz#68a486714d7a7fd1df56cb9bc89a860a0de866de" + integrity sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw== + +"@octokit/core@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.3.tgz#280d3bb66c702297baac0a202219dd66611286e4" + integrity sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow== + dependencies: + "@octokit/auth-token" "^5.0.0" + "@octokit/graphql" "^8.1.2" + "@octokit/request" "^9.1.4" + "@octokit/request-error" "^6.1.6" + "@octokit/types" "^13.6.2" + before-after-hook "^3.0.2" + universal-user-agent "^7.0.0" + +"@octokit/endpoint@^10.0.0": + version "10.1.2" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.2.tgz#d38e727e2a64287114fdaa1eb9cd7c81c09460df" + integrity sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA== + dependencies: + "@octokit/types" "^13.6.2" + universal-user-agent "^7.0.2" + +"@octokit/graphql@^8.1.2": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.2.0.tgz#983a7ebc6479338d78921a1ca9b4095f85991e28" + integrity sha512-gejfDywEml/45SqbWTWrhfwvLBrcGYhOn50sPOjIeVvH6i7D16/9xcFA8dAJNp2HMcd+g4vru41g4E2RBiZvfQ== + dependencies: + "@octokit/request" "^9.1.4" + "@octokit/types" "^13.8.0" + universal-user-agent "^7.0.0" + +"@octokit/openapi-types@^23.0.1": + version "23.0.1" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-23.0.1.tgz#3721646ecd36b596ddb12650e0e89d3ebb2dd50e" + integrity sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g== + +"@octokit/request-error@^6.0.1", "@octokit/request-error@^6.1.6": + version "6.1.6" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.6.tgz#5f42c7894e7c3ab47c63aa3241f78cee8a826644" + integrity sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg== + dependencies: + "@octokit/types" "^13.6.2" + +"@octokit/request@^9.1.4": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.2.0.tgz#21aa1e72ff645f5b99ccf4a590cc33c4578bb356" + integrity sha512-kXLfcxhC4ozCnAXy2ff+cSxpcF0A1UqxjvYMqNuPIeOAzJbVWQ+dy5G2fTylofB/gTbObT8O6JORab+5XtA1Kw== + dependencies: + "@octokit/endpoint" "^10.0.0" + "@octokit/request-error" "^6.0.1" + "@octokit/types" "^13.6.2" + fast-content-type-parse "^2.0.0" + universal-user-agent "^7.0.2" + +"@octokit/types@^13.6.2", "@octokit/types@^13.8.0": + version "13.8.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.8.0.tgz#3815885e5abd16ed9ffeea3dced31d37ce3f8a0a" + integrity sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A== + dependencies: + "@octokit/openapi-types" "^23.0.1" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -1184,6 +1256,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@sindresorhus/is@^5.2.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668" @@ -1304,6 +1381,13 @@ dependencies: "@swc/counter" "^0.1.3" +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@szmarczak/http-timer@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" @@ -1384,6 +1468,16 @@ "@types/connect" "*" "@types/node" "*" +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/connect@*": version "3.4.38" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" @@ -1451,7 +1545,7 @@ dependencies: "@types/node" "*" -"@types/http-cache-semantics@^4.0.2": +"@types/http-cache-semantics@*", "@types/http-cache-semantics@^4.0.2": version "4.0.4" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== @@ -1493,6 +1587,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/methods@^1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" @@ -1517,6 +1618,11 @@ dependencies: "@types/node" "*" +"@types/parse-path@^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/parse-path/-/parse-path-7.0.3.tgz#cec2da2834ab58eb2eb579122d9a1fc13bd7ef36" + integrity sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg== + "@types/passport-github2@^1.2.9": version "1.2.9" resolved "https://registry.yarnpkg.com/@types/passport-github2/-/passport-github2-1.2.9.tgz#7e43b8529276cc8c429ac430f9de06d8406a17da" @@ -1569,6 +1675,13 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + "@types/send@*": version "0.17.4" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" @@ -2278,6 +2391,16 @@ bcrypt@^5.1.1: "@mapbox/node-pre-gyp" "^1.0.11" node-addon-api "^5.0.0" +before-after-hook@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + +before-after-hook@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d" + integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A== + bin-version-check@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-5.1.0.tgz#788e80e036a87313f8be7908bc20e5abe43f0837" @@ -2300,6 +2423,17 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== +bitbucket@^2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/bitbucket/-/bitbucket-2.12.0.tgz#bb13796502c1d3ace0143fc01777140e7e18e78b" + integrity sha512-YqaiTPEmn5mkwdU2gGcJZcQ6B8/DhCHhc3SSYqSpnef6nSTTSa/2GSBoLEgPLqAuqrQITGKq8MgYkfDMtnJPuw== + dependencies: + before-after-hook "^2.1.0" + deepmerge "^4.2.2" + is-plain-object "^3.0.0" + node-fetch "^2.6.0" + url-template "^2.0.8" + bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -2406,6 +2540,11 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + cacheable-lookup@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" @@ -2424,6 +2563,19 @@ cacheable-request@^10.2.8: normalize-url "^8.0.0" responselike "^3.0.0" +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + 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" + call-bind-apply-helpers@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" @@ -2565,6 +2717,13 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -2755,6 +2914,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-fetch@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.1.0.tgz#8f69355007ee182e47fa692ecbaa37a52e43c3d2" + integrity sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw== + dependencies: + node-fetch "^2.7.0" + cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -2840,7 +3006,7 @@ defaults@^3.0.0: resolved "https://registry.yarnpkg.com/defaults/-/defaults-3.0.0.tgz#60b9e0003df1018737c2ce3f4289d8f64786c9c4" integrity sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A== -defer-to-connect@^2.0.1: +defer-to-connect@^2.0.0, defer-to-connect@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== @@ -2962,6 +3128,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + engine.io-parser@~5.2.1: version "5.2.3" resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" @@ -3279,6 +3452,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== +fast-content-type-parse@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz#c236124534ee2cb427c8d8e5ba35a4856947847b" + integrity sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3604,6 +3782,13 @@ get-proto@^1.0.0: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -3624,6 +3809,26 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +git-up@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-8.0.0.tgz#674d398f95c4f70b4193f3f3d87c73cf28c2bee1" + integrity sha512-uBI8Zdt1OZlrYfGcSVroLJKgyNNXlgusYFzHk614lTasz35yg2PVpL1RMy0LOO2dcvF9msYW3pRfUSmafZNrjg== + dependencies: + is-ssh "^1.4.0" + parse-url "^9.2.0" + +git-url-parse@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-16.0.0.tgz#04dcc54197ad9aa2c92795b32be541d217c11f70" + integrity sha512-Y8iAF0AmCaqXc6a5GYgPQW9ESbncNLOL+CeQAJRhmWUOmnPkKpBYeWYp4mFd3LA5j53CdGDdslzX12yEBVHQQg== + dependencies: + git-up "^8.0.0" + +gitea-js@^1.23.0: + version "1.23.0" + resolved "https://registry.yarnpkg.com/gitea-js/-/gitea-js-1.23.0.tgz#44914028f4c4675ccb01ee2ac4041aedd0bab95a" + integrity sha512-f4+UPoWgDetZeZ+Awo5iI1nVdO5bjxA8+2QCeLo3oYWUYxKyzLfXgbW1EPD635wb8hLgS0DRBu5XhtiuYKEeUA== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3699,6 +3904,23 @@ gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== +got@^11.8.1: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + 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" + got@^13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/got/-/got-13.0.0.tgz#a2402862cef27a5d0d1b07c0fb25d12b58175422" @@ -3776,7 +3998,7 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-cache-semantics@^4.1.1: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== @@ -3801,6 +4023,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + http2-wrapper@^2.1.10: version "2.2.1" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" @@ -3955,11 +4185,23 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== +is-plain-object@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" + integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== + is-promise@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-ssh@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" + integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== + dependencies: + protocols "^2.0.1" + is-stream@^2.0.0, is-stream@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -4564,7 +4806,7 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" -keyv@^4.5.3, keyv@^4.5.4: +keyv@^4.0.0, keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -4641,6 +4883,11 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lowercase-keys@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" @@ -4787,6 +5034,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -4950,7 +5202,7 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.7: +node-fetch@^2.6.0, node-fetch@^2.6.7, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -4979,6 +5231,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + normalize-url@^8.0.0: version "8.0.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.1.tgz#9b7d96af9836577c58f5883e939365fa15623a4a" @@ -5038,7 +5295,7 @@ on-finished@2.4.1, on-finished@^2.4.1: dependencies: ee-first "1.1.1" -once@1.4.0, once@^1.3.0, once@^1.4.0: +once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -5092,6 +5349,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + p-cancelable@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" @@ -5152,6 +5414,21 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-path@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" + integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog== + dependencies: + protocols "^2.0.0" + +parse-url@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-9.2.0.tgz#d75da32b3bbade66e4eb0763fb4851d27526b97b" + integrity sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ== + dependencies: + "@types/parse-path" "^7.0.0" + parse-path "^7.0.0" + parseurl@^1.3.3, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -5340,6 +5617,11 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +protocols@^2.0.0, protocols@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" + integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -5360,6 +5642,14 @@ psl@^1.1.28: dependencies: punycode "^2.3.1" +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -5506,7 +5796,7 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -resolve-alpn@^1.2.0: +resolve-alpn@^1.0.0, resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== @@ -5542,6 +5832,13 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + responselike@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" @@ -6376,6 +6673,11 @@ undici-types@~6.20.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz#52e7d0e9b3dc4df06cc33cb2b9fd79041a54827e" + integrity sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -6401,6 +6703,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-template@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" + integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw== + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 9b55cfb2..5fd7725c 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -1194,6 +1194,7 @@ export class Kubero { return loglines; } + //Migration to repo public getRepositories() { let repositories = { github: false, diff --git a/server/src/types.ts b/server/src/types.ts index 8be936da..a8907cea 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -443,6 +443,7 @@ export interface IKuberoConfig { } } +//Migrated to repo export interface IDeployKeyPair { fingerprint: string; pubKey: string; From f52b8083b13908376944e8b98b042dddad6d2aca Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sun, 9 Feb 2025 00:25:14 +0100 Subject: [PATCH 23/65] migrated notifications and pipelines partialy --- server-refactored-v3/src/app.module.ts | 2 + .../src/audit/audit.module.ts | 3 +- .../src/auth/auth.interface.ts | 6 + server-refactored-v3/src/auth/auth.module.ts | 4 +- .../src/events/events.gateway.ts | 4 + .../notifications/notifications.interface.ts | 35 ++++ .../src/notifications/notifications.module.ts | 10 ++ .../notifications.service.spec.ts | 18 ++ .../notifications/notifications.service.ts | 168 ++++++++++++++++++ .../src/pipelines/pipelines.controller.ts | 16 +- .../src/pipelines/pipelines.service.ts | 161 ++++++++++++++++- .../src/repo/git/bitbucket.ts | 4 +- server-refactored-v3/src/repo/git/gitlab.ts | 5 +- .../src/settings/settings.interface.ts | 25 +-- server/src/kubero.ts | 1 + server/src/modules/auth.ts | 1 + server/src/modules/notifications.ts | 2 +- server/src/routes/pipelines.ts | 3 + server/src/types.ts | 9 +- 19 files changed, 435 insertions(+), 42 deletions(-) create mode 100644 server-refactored-v3/src/auth/auth.interface.ts create mode 100644 server-refactored-v3/src/notifications/notifications.interface.ts create mode 100644 server-refactored-v3/src/notifications/notifications.module.ts create mode 100644 server-refactored-v3/src/notifications/notifications.service.spec.ts create mode 100644 server-refactored-v3/src/notifications/notifications.service.ts diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 413aa2a0..65dc937f 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -19,6 +19,7 @@ import { CoreModule } from './core/core.module'; import { KubernetesModule } from './kubernetes/kubernetes.module'; import { AuditModule } from './audit/audit.module'; import { AddonsModule } from './addons/addons.module'; +import { NotificationsModule } from './notifications/notifications.module'; @Module({ @@ -42,6 +43,7 @@ import { AddonsModule } from './addons/addons.module'; KubernetesModule, AuditModule, AddonsModule, + NotificationsModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/audit/audit.module.ts b/server-refactored-v3/src/audit/audit.module.ts index f321fa7d..8e64cdd1 100644 --- a/server-refactored-v3/src/audit/audit.module.ts +++ b/server-refactored-v3/src/audit/audit.module.ts @@ -5,6 +5,7 @@ import { AuditController } from './audit.controller'; @Global() @Module({ providers: [AuditService], - controllers: [AuditController] + controllers: [AuditController], + exports: [AuditService], }) export class AuditModule {} diff --git a/server-refactored-v3/src/auth/auth.interface.ts b/server-refactored-v3/src/auth/auth.interface.ts new file mode 100644 index 00000000..155c1a38 --- /dev/null +++ b/server-refactored-v3/src/auth/auth.interface.ts @@ -0,0 +1,6 @@ +export type IUser = { + id: number, + method: string, + username: string, + apitoken?: string +} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index e8136ff4..3e849ed5 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -6,11 +6,11 @@ import { SettingsService } from '../settings/settings.service'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; import { AuthController } from './auth.controller'; -import { AuditService } from 'src/audit/audit.service'; +import { AuditModule } from 'src/audit/audit.module'; @Module({ imports: [UsersModule, PassportModule ], - providers: [AuthService, LocalStrategy, KubernetesModule, AuditService, SettingsService], + providers: [AuthService, LocalStrategy, KubernetesModule, AuditModule, SettingsService], controllers: [AuthController], }) export class AuthModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/events/events.gateway.ts b/server-refactored-v3/src/events/events.gateway.ts index fb57f0be..85a37789 100644 --- a/server-refactored-v3/src/events/events.gateway.ts +++ b/server-refactored-v3/src/events/events.gateway.ts @@ -24,4 +24,8 @@ export class EventsGateway { findAll(@MessageBody() data: any): Observable> { return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item }))); } + + sendEvent(event: string, data: any) { + this.server.emit(event, data); + } } \ No newline at end of file diff --git a/server-refactored-v3/src/notifications/notifications.interface.ts b/server-refactored-v3/src/notifications/notifications.interface.ts new file mode 100644 index 00000000..8686fd7b --- /dev/null +++ b/server-refactored-v3/src/notifications/notifications.interface.ts @@ -0,0 +1,35 @@ +export interface INotification { + name: string, + user: string, + resource: "system" | "app" | "pipeline" | "phase" | "namespace" | "build" | "addon" | "settings" | "user" | "events" | "security" | "templates" | "config" | "addons" | "kubernetes" | "unknown", + action: string, + severity: "normal" | "info" | "warning" | "critical" | "error" | "unknown", + message: string, + phaseName: string, + pipelineName: string, + appName: string, + data?: any +} + +export interface INotificationConfig{ + enabled: boolean; + name: string; + type: 'slack' | 'webhook' | 'discord', + pipelines: string[], + events: string[], + config: INotificationSlack | INotificationWebhook | INotificationDiscord; +} + +export interface INotificationSlack { + url: string; + channel: string; +} + +export interface INotificationWebhook { + url: string; + secret: string; +} + +export interface INotificationDiscord { + url: string; +} diff --git a/server-refactored-v3/src/notifications/notifications.module.ts b/server-refactored-v3/src/notifications/notifications.module.ts new file mode 100644 index 00000000..5f71678e --- /dev/null +++ b/server-refactored-v3/src/notifications/notifications.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { NotificationsService } from './notifications.service'; +import { EventsGateway } from '../events/events.gateway'; +import { AuditModule } from '../audit/audit.module'; +import { KubernetesModule } from '../kubernetes/kubernetes.module'; + +@Module({ + providers: [NotificationsService, EventsGateway, AuditModule, KubernetesModule], +}) +export class NotificationsModule {} diff --git a/server-refactored-v3/src/notifications/notifications.service.spec.ts b/server-refactored-v3/src/notifications/notifications.service.spec.ts new file mode 100644 index 00000000..47119104 --- /dev/null +++ b/server-refactored-v3/src/notifications/notifications.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { NotificationsService } from './notifications.service'; + +describe('NotificationsService', () => { + let service: NotificationsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [NotificationsService], + }).compile(); + + service = module.get(NotificationsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/notifications/notifications.service.ts b/server-refactored-v3/src/notifications/notifications.service.ts new file mode 100644 index 00000000..879d3108 --- /dev/null +++ b/server-refactored-v3/src/notifications/notifications.service.ts @@ -0,0 +1,168 @@ +import { Injectable } from '@nestjs/common'; +import { AuditService } from 'src/audit/audit.service'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; +import { INotificationConfig, INotification, INotificationSlack, INotificationWebhook, INotificationDiscord } from './notifications.interface'; +import { EventsGateway } from '../events/events.gateway'; +import { IKuberoConfig } from '../settings/settings.interface'; +import fetch from 'node-fetch'; + +@Injectable() +export class NotificationsService { + + //public kubectl: Kubectl; + //private audit: Audit; + private config: IKuberoConfig; + + constructor( + private eventsGateway: EventsGateway, + private auditService: AuditService, + private kubectl: KubernetesService, + ) { + this.config = {} as IKuberoConfig; + } + + public setConfig(config: IKuberoConfig) { + this.config = config; + } + + public send(message: INotification) { + this.sendWebsocketMessage(message); + this.createKubernetesEvent(message); + this.writeAuditLog(message) + + this.sendAllCustomNotification(this.config.notifications, message); + + /* requires configuration in pipeline and app form + if (message.data && message.data.app && message.data.app.notifications) { + this.sendAllCustomNotification(message.data.app.notifications, message); + } + + if (message.data && message.data.pipeline && message.data.pipeline.notifications) { + this.sendAllCustomNotification(message.data.pipeline.notifications, message); + } + */ + } + private sendWebsocketMessage(n: INotification) { + this.eventsGateway.sendEvent(n.name, n); + } + + private createKubernetesEvent(n: INotification) { + this.kubectl.createEvent( + 'Normal', + n.action.replace(/^./, str => str.toUpperCase()), + n.name, + n.message, + ); + } + + private writeAuditLog(n: INotification) { + this.auditService.log({ + action: n.action, + user: n.user, + severity: n.severity, + namespace: n.appName+'-'+n.phaseName, + phase: n.phaseName, + app: n.appName, + pipeline: n.pipelineName, + resource: n.resource, + message: n.message, + }); + } + + public sendDelayed(message: INotification) { + setTimeout(() => { + this.send(message); + }, 1000); + } + + private sendAllCustomNotification(notifications: INotificationConfig[], message: INotification) { + if (!notifications) { + return; + } + notifications.forEach(notification => { + if (notification.enabled && + notification.events && + notification.events?.includes(message.name) && + (notification.pipelines?.length == 0 || notification.pipelines?.includes('all') || notification.pipelines?.includes(message.pipelineName)) + ) { + this.sendCustomNotification(notification.type, + notification.config, + { + name: notification.name, + user: message.user, + resource: message.resource, + action: message.action, + severity: message.severity, + message: message.message, + phaseName: message.phaseName, + pipelineName: message.pipelineName, + appName: message.appName, + data: message.data + }); + } + }); + } + + private sendCustomNotification(type: string, config: any, message: INotification) { + switch (type) { + case 'slack': + this.sendSlackNotification(message, config as INotificationSlack); + break; + case 'webhook': + this.sendWebhookNotification(message, config as INotificationWebhook); + break; + case 'discord': + this.sendDiscordNotification(message, config as INotificationDiscord); + break; + default: + console.log('unknown notification type', type); + break; + } + } + + private sendSlackNotification(message: INotification, config: INotificationSlack) { + // Docs: https://api.slack.com/messaging/webhooks#posting_with_webhooks + fetch(config.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + text: message.message, + }) + }) + .then( res => console.log('Slack notification sent to '+config.url+' with status '+res.status)) + //.then(json => console.log(json)); + .catch( err => console.log('Slack notification failed to '+config.url+' with error '+err)) + } + + private sendWebhookNotification(message: INotification, config: INotificationWebhook) { + fetch(config.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: message, + secret: config.secret + }) + }) + .then( res => console.log('Webhook notification sent to '+config.url+' with status '+res.status)) + .catch( err => console.log('Webhook notification failed to '+config.url+' with error '+err)) + } + + private sendDiscordNotification(message: INotification, config: INotificationDiscord) { + //Docs: https://discord.com/developers/docs/resources/webhook#execute-webhook + fetch(config.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + content: message.message, + }) + }) + .then( res => console.log('Discord notification sent to '+config.url+' with status '+res.status)) + .catch( err => console.log('Discord notification failed to '+config.url+' with error '+err)) + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/pipelines/pipelines.controller.ts b/server-refactored-v3/src/pipelines/pipelines.controller.ts index 613d20bd..32dc5445 100644 --- a/server-refactored-v3/src/pipelines/pipelines.controller.ts +++ b/server-refactored-v3/src/pipelines/pipelines.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Delete, Get, Post, Put } from '@nestjs/common'; +import { Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import { PipelinesService } from './pipelines.service'; import { ApiOperation, ApiResponse } from '@nestjs/swagger'; @@ -19,10 +19,12 @@ export class PipelinesController { return 'Pipeline updated'; } - @ApiOperation({ summary: 'Get a pipeline' }) + @ApiOperation({ summary: 'Get a soecific pipeline' }) @Get('/:pipeline') - async getPipeline() { - return 'Pipeline'; + async getPipeline( + @Param('pipeline') pipeline: string, + ) { + return this.pipelinesService.getPipeline(pipeline); } @ApiOperation({ summary: 'Update a pipeline' }) @@ -39,7 +41,9 @@ export class PipelinesController { @ApiOperation({ summary: 'Get all apps for a pipeline' }) @Get('/:pipeline/apps') - async getPipelineApps() { - return 'Pipeline apps'; + async getPipelineApps( + @Param('pipeline') pipeline: string, + ) { + return this.pipelinesService.getPipelineWithApps(pipeline); } } diff --git a/server-refactored-v3/src/pipelines/pipelines.service.ts b/server-refactored-v3/src/pipelines/pipelines.service.ts index 34d4f3b5..3ac14981 100644 --- a/server-refactored-v3/src/pipelines/pipelines.service.ts +++ b/server-refactored-v3/src/pipelines/pipelines.service.ts @@ -1,14 +1,18 @@ import { Injectable, Logger } from '@nestjs/common'; -import { IPipelineList } from './pipelines.interface'; +import { IPipelineList, IPipeline } from './pipelines.interface'; import { KubernetesService } from 'src/kubernetes/kubernetes.service'; +import { Buildpack } from '../settings/buildpack/buildpack'; +import { IUser } from 'src/auth/auth.interface'; @Injectable() export class PipelinesService { private readonly logger = new Logger(PipelinesService.name); + //private pipelineStateList = [] as IPipeline[]; //DEPRECATED: should not be used but reloaded live state constructor(private kubectl: KubernetesService) {} public async listPipelines(): Promise { + this.logger.debug('listPipelines'); let pipelines = await this.kubectl.getPipelinesList(); const ret: IPipelineList = { items: new Array() @@ -19,4 +23,159 @@ export class PipelinesService { } return ret; } + + public async getPipelineWithApps(pipelineName: string) { + this.logger.debug('listApps in '+pipelineName); + + await this.kubectl.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + const kpipeline = await this.kubectl.getPipeline(pipelineName); + + if (!kpipeline.spec || !kpipeline.spec.git || !kpipeline.spec.git.keys) { + return; + } + + delete kpipeline.spec.git.keys.priv + delete kpipeline.spec.git.keys.pub + + let pipeline = kpipeline.spec + + if (pipeline) { + for (const phase of pipeline.phases) { + if (phase.enabled == true) { + + const contextName = await this.getContext(pipelineName, phase.name); + if (contextName) { + const namespace = pipelineName+'-'+phase.name; + let apps = await this.kubectl.getAppsList(namespace, contextName); + + let appslist = new Array(); + for (const app of apps.items) { + appslist.push(app.spec); + } + // @ts-expect-error ts(2532) FIXME: Object is possibly 'undefined'. + pipeline.phases.find(p => p.name == phase.name).apps = appslist; + + } + } + } + } + return pipeline; + } + + public async getContext(pipelineName: string, phaseName: string): Promise { + let context: string = 'missing-'+pipelineName+'-'+phaseName; + const pipelinesList = await this.listPipelines() + + for (const pipeline of pipelinesList.items) { + if (pipeline.name == pipelineName) { + for (const phase of pipeline.phases) { + if (phase.name == phaseName) { + //this.kubectl.setCurrentContext(phase.context); + context = phase.context; + } + } + } + } + return context + } + + public async getPipeline(pipelineName: string): Promise{ + this.logger.debug('getPipeline: '+pipelineName); + + let pipeline = await this.kubectl.getPipeline(pipelineName) + .catch(error => { + this.logger.error(error); + return undefined; + }); + + if (pipeline) { + if (pipeline.spec.buildpack) { + pipeline.spec.buildpack.fetch.securityContext = Buildpack.SetSecurityContext(pipeline.spec.buildpack.fetch.securityContext); + pipeline.spec.buildpack.build.securityContext = Buildpack.SetSecurityContext(pipeline.spec.buildpack.build.securityContext); + pipeline.spec.buildpack.run.securityContext = Buildpack.SetSecurityContext(pipeline.spec.buildpack.run.securityContext); + } + + if (pipeline.metadata && pipeline.metadata.resourceVersion) { + pipeline.spec.resourceVersion = pipeline.metadata.resourceVersion; + } + + delete pipeline.spec.git.keys.priv + delete pipeline.spec.git.keys.pub + return pipeline.spec; + } + } + + // delete a pipeline and all its namespaces/phases + public deletePipeline(pipelineName: string, user: IUser) { + this.logger.debug('deletePipeline: '+pipelineName); + + if ( process.env.KUBERO_READONLY == 'true'){ + console.log('KUBERO_READONLY is set to true, not deleting pipeline '+ pipelineName); + return; + } + + this.kubectl.getPipeline(pipelineName).then(async pipeline =>{ + if (pipeline) { + await this.kubectl.deletePipeline(pipelineName); + + await new Promise(resolve => setTimeout(resolve, 5000)); // needs some extra time to delete the namespace + //this.updateState(); + + /* Might be moved to a notification middleware + const m = { + 'name': 'deletePipeline', + 'user': user.username, + 'resource': 'pipeline', + 'action': 'delete', + 'severity': 'normal', + 'message': 'Deleted pipeline: '+pipelineName, + 'pipelineName':pipelineName, + 'phaseName': '', + 'appName': '', + 'data': { + 'pipeline': pipeline + } + } as INotification; + this.notification.send(m, this._io); + */ + } + }) + .catch(error => { + this.logger.error(error); + }); + } + + /* + public updateState() { + this.pipelineStateList = []; + this.appStateList = []; + this.listPipelines().then(pl => { + for (const pipeline of pl.items as IPipeline[]) { + this.pipelineStateList.push(pipeline); + + for (const phase of pipeline.phases) { + + if (phase.enabled == true) { + debug.log("🔁 Loading Namespace: "+pipeline.name+"-"+phase.name); + this.listAppsInNamespace(pipeline.name, phase.name) + .then(appsList => { + if (appsList) { + for (const app of appsList.items) { + debug.log("🔁 Loading App: "+app.spec.name); + this.appStateList.push(app.spec); + } + } + }) + .catch(error => { + debug.log(error); + }) + } + } + } + } + ).catch(error => { + debug.log(error); + }); + } + */ } diff --git a/server-refactored-v3/src/repo/git/bitbucket.ts b/server-refactored-v3/src/repo/git/bitbucket.ts index 74ff0856..0eccf3b0 100644 --- a/server-refactored-v3/src/repo/git/bitbucket.ts +++ b/server-refactored-v3/src/repo/git/bitbucket.ts @@ -8,6 +8,7 @@ debug('app:kubero:bitbucket:api') //const { Octokit } = require("@octokit/core"); import { Bitbucket, APIClient } from "bitbucket" import { RequestError } from '@octokit/types'; +import { Logger } from '@nestjs/common'; export class BitbucketApi extends Repo { private bitbucket: APIClient; @@ -22,10 +23,11 @@ export class BitbucketApi extends Repo { } if (process.env.BITBUCKET_USERNAME && process.env.BITBUCKET_APP_PASSWORD) { + Logger.log('✅ BitBucket enabled', 'Feature'); this.bitbucket = new Bitbucket(clientOptions) } else { this.bitbucket = new Bitbucket() - console.log("☑️ Feature: BitBucket disabled: No BITBUCKET_USERNAME or BITBUCKET_APP_PASSWORD set") + Logger.log("☑️ BitBucket disabled: No BITBUCKET_USERNAME or BITBUCKET_APP_PASSWORD set", 'Feature'); } } diff --git a/server-refactored-v3/src/repo/git/gitlab.ts b/server-refactored-v3/src/repo/git/gitlab.ts index e09e49e5..599b4fba 100644 --- a/server-refactored-v3/src/repo/git/gitlab.ts +++ b/server-refactored-v3/src/repo/git/gitlab.ts @@ -6,6 +6,7 @@ import { Repo } from './repo'; import {Client as GitlabClient} from '@nerdvision/gitlab-js'; import {Options} from 'got'; import gitUrlParse = require("git-url-parse"); +import { Logger } from '@nestjs/common'; export class GitlabApi extends Repo { @@ -21,9 +22,9 @@ export class GitlabApi extends Repo { const host = baseURL || 'https://gitlab.com'; if (token == undefined) { - console.log('☑️ Feature: Gitlab not configured (no token)'); + Logger.log('☑️ Feature: Gitlab not configured (no token)', 'Feature'); } else { - console.log('✅ Feature: Gitlab configured: '+host); + Logger.log('✅ Feature: Gitlab configured: '+host, 'Feature'); } this.gitlab = new GitlabClient({ diff --git a/server-refactored-v3/src/settings/settings.interface.ts b/server-refactored-v3/src/settings/settings.interface.ts index 646c8d3d..117aed86 100644 --- a/server-refactored-v3/src/settings/settings.interface.ts +++ b/server-refactored-v3/src/settings/settings.interface.ts @@ -1,3 +1,4 @@ +import { INotificationConfig } from '../notifications/notifications.interface'; export interface IKuberoConfig { podSizeList: IPodSize[]; buildpacks: IBuildpack[]; @@ -68,30 +69,6 @@ export type IKuberoCRD = { } } - -interface INotificationConfig{ - enabled: boolean; - name: string; - type: 'slack' | 'webhook' | 'discord', - pipelines: string[], - events: string[], - config: INotificationSlack | INotificationWebhook | INotificationDiscord; -} - -interface INotificationSlack { - url: string; - channel: string; -} - -interface INotificationWebhook { - url: string; - secret: string; -} - -interface INotificationDiscord { - url: string; -} - export interface IPodSize { name: string; description: string, diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 5fd7725c..6bba9885 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -297,6 +297,7 @@ export class Kubero { } } + //Migrated to pipelines // delete a pipeline and all its namespaces/phases public deletePipeline(pipelineName: string, user: User) { debug.debug('deletePipeline: '+pipelineName); diff --git a/server/src/modules/auth.ts b/server/src/modules/auth.ts index 39e6431c..d9c0ee22 100644 --- a/server/src/modules/auth.ts +++ b/server/src/modules/auth.ts @@ -10,6 +10,7 @@ import { Request, Response, NextFunction } from 'express'; import * as crypto from "crypto" import axios from 'axios'; +//Migrated to auth export type User = { id: number, method: string, diff --git a/server/src/modules/notifications.ts b/server/src/modules/notifications.ts index 7056cfcf..75aafdde 100644 --- a/server/src/modules/notifications.ts +++ b/server/src/modules/notifications.ts @@ -3,7 +3,7 @@ import { Server } from "socket.io"; import { Kubectl } from "./kubectl"; import { IKuberoConfig, INotificationSlack, INotificationWebhook, INotificationDiscord, INotificationConfig} from "../types"; - +//Migrated to notifications export interface INotification { name: string, user: string, diff --git a/server/src/routes/pipelines.ts b/server/src/routes/pipelines.ts index 8e0c10e5..2be03f5f 100644 --- a/server/src/routes/pipelines.ts +++ b/server/src/routes/pipelines.ts @@ -129,6 +129,7 @@ Router.get('/cli/pipelines', bearerMiddleware, async function (req: Request, res res.send(pipelines); }); +//Migrated to pipelines Router.get('/pipelines', authMiddleware, async function (req: Request, res: Response) { // #swagger.tags = ['UI'] // #swagger.summary = 'Get a list of available pipelines' @@ -153,6 +154,7 @@ Router.get('/cli/pipelines/:pipeline', bearerMiddleware, async function (req: Re res.send(pipeline); }); +//Migrated to pipelines Router.get('/pipelines/:pipeline', authMiddleware, async function (req: Request, res: Response) { // #swagger.tags = ['UI'] // #swagger.summary = 'Get a pipeline' @@ -325,6 +327,7 @@ Router.get('/cli/pipelines/:pipeline/apps', bearerMiddleware, async function (re } }); +//Migrated to pipelines Router.get('/pipelines/:pipeline/apps', authMiddleware, async function (req: Request, res: Response) { // #swagger.tags = ['UI'] // #swagger.summary = 'Get all apps in a pipeline' diff --git a/server/src/types.ts b/server/src/types.ts index a8907cea..9b8d4db3 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -366,23 +366,24 @@ export interface IBuildpack { tag: string; } -//Migrated to settings +//Migrated to notifications export interface INotificationSlack { url: string; channel: string; } -//Migrated to settings +//Migrated to notifications export interface INotificationWebhook { url: string; secret: string; } -//Migrated to settings +//Migrated to notifications export interface INotificationDiscord { url: string; } +//Not used!! export interface INotification { action: string; user: string; @@ -395,7 +396,7 @@ export interface INotification { message: string; } -//Migrated to settings +//Migrated to notifications export interface INotificationConfig{ enabled: boolean; name: string; From 46fac4680bb08a24e6eb8ebc5cd4838113d1210b Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sun, 9 Feb 2025 22:36:13 +0100 Subject: [PATCH 24/65] migrated app overview --- server-refactored-v3/package.json | 1 + server-refactored-v3/src/app.module.ts | 4 +- server-refactored-v3/src/apps/apps.module.ts | 8 +- .../src/apps/apps.service.spec.ts | 69 ++++ server-refactored-v3/src/apps/apps.service.ts | 24 ++ .../src/metrics/metrics.controller.spec.ts | 18 + .../src/metrics/metrics.controller.ts | 20 + .../src/metrics/metrics.interface.ts | 31 ++ .../src/metrics/metrics.module.ts | 8 +- .../src/metrics/metrics.service.spec.ts | 18 + .../src/metrics/metrics.service.ts | 361 ++++++++++++++++++ .../src/pipelines/pipelines.module.ts | 6 +- .../src/pipelines/pipelines.service.ts | 2 - .../src/repo/git/bitbucket.ts | 4 +- .../src/repo/repo.controller.ts | 27 ++ server-refactored-v3/src/repo/repo.service.ts | 4 +- .../src/security/security.controller.spec.ts | 18 + .../src/security/security.controller.ts | 22 ++ .../src/security/security.module.ts | 12 + .../src/security/security.service.spec.ts | 18 + .../src/security/security.service.ts | 125 ++++++ .../vulnerabilities/vulnerabilities.module.ts | 4 - server-refactored-v3/yarn.lock | 9 +- server/src/kubero.ts | 2 + 24 files changed, 798 insertions(+), 17 deletions(-) create mode 100644 server-refactored-v3/src/apps/apps.service.spec.ts create mode 100644 server-refactored-v3/src/apps/apps.service.ts create mode 100644 server-refactored-v3/src/metrics/metrics.controller.spec.ts create mode 100644 server-refactored-v3/src/metrics/metrics.controller.ts create mode 100644 server-refactored-v3/src/metrics/metrics.interface.ts create mode 100644 server-refactored-v3/src/metrics/metrics.service.spec.ts create mode 100644 server-refactored-v3/src/metrics/metrics.service.ts create mode 100644 server-refactored-v3/src/security/security.controller.spec.ts create mode 100644 server-refactored-v3/src/security/security.controller.ts create mode 100644 server-refactored-v3/src/security/security.module.ts create mode 100644 server-refactored-v3/src/security/security.service.spec.ts create mode 100644 server-refactored-v3/src/security/security.service.ts delete mode 100644 server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index 4aa58e50..c47f7b3d 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -45,6 +45,7 @@ "passport-github2": "^0.1.12", "passport-local": "^1.0.0", "passport-oauth2": "^1.8.0", + "prometheus-query": "^3.4.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "yaml": "^2.7.0" diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 65dc937f..ac1ee70c 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -7,7 +7,6 @@ import { ServeStaticModule } from '@nestjs/serve-static'; import { AuthModule } from './auth/auth.module'; import { AppsModule } from './apps/apps.module'; import { PipelinesModule } from './pipelines/pipelines.module'; -import { VulnerabilitiesModule } from './vulnerabilities/vulnerabilities.module'; import { ConfigModule } from './config/config.module'; import { RepoModule } from './repo/repo.module'; import { SettingsModule } from './settings/settings.module'; @@ -20,6 +19,7 @@ import { KubernetesModule } from './kubernetes/kubernetes.module'; import { AuditModule } from './audit/audit.module'; import { AddonsModule } from './addons/addons.module'; import { NotificationsModule } from './notifications/notifications.module'; +import { SecurityModule } from './security/security.module'; @Module({ @@ -32,7 +32,6 @@ import { NotificationsModule } from './notifications/notifications.module'; AuthModule, AppsModule, PipelinesModule, - VulnerabilitiesModule, ConfigModule, RepoModule, SettingsModule, @@ -44,6 +43,7 @@ import { NotificationsModule } from './notifications/notifications.module'; AuditModule, AddonsModule, NotificationsModule, + SecurityModule, ], controllers: [AppController], providers: [AppService], diff --git a/server-refactored-v3/src/apps/apps.module.ts b/server-refactored-v3/src/apps/apps.module.ts index f84eeff0..1e8d579a 100644 --- a/server-refactored-v3/src/apps/apps.module.ts +++ b/server-refactored-v3/src/apps/apps.module.ts @@ -1,4 +1,10 @@ import { Module } from '@nestjs/common'; +import { AppsService } from './apps.service'; +import { KubernetesModule } from '../kubernetes/kubernetes.module'; +import { PipelinesService } from '../pipelines/pipelines.service'; -@Module({}) +@Module({ + providers: [AppsService, KubernetesModule, PipelinesService], + exports: [AppsService], +}) export class AppsModule {} diff --git a/server-refactored-v3/src/apps/apps.service.spec.ts b/server-refactored-v3/src/apps/apps.service.spec.ts new file mode 100644 index 00000000..6a78ffb3 --- /dev/null +++ b/server-refactored-v3/src/apps/apps.service.spec.ts @@ -0,0 +1,69 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppsService } from './apps.service'; +import { PipelinesService } from '../pipelines/pipelines.service'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; + +describe('AppsService', () => { + let service: AppsService; + let kubernetesService: KubernetesService; + let pipelinesService: PipelinesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AppsService, + { + provide: KubernetesService, + useValue: { + getApp: jest.fn(), + }, + }, + { + provide: PipelinesService, + useValue: { + getContext: jest.fn(), + }, + }, + ], + }).compile(); + + service = module.get(AppsService); + kubernetesService = module.get(KubernetesService); + pipelinesService = module.get(PipelinesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should get app', async () => { + const pipelineName = 'test-pipeline'; + const phaseName = 'test-phase'; + const appName = 'test-app'; + const contextName = 'test-context'; + const app = { response: { statusCode: 200 } as any, body: { name: appName } }; + + jest.spyOn(pipelinesService, 'getContext').mockResolvedValue(contextName); + jest.spyOn(kubernetesService, 'getApp').mockResolvedValue(app); + + const result = await service.getApp(pipelineName, phaseName, appName); + + expect(pipelinesService.getContext).toHaveBeenCalledWith(pipelineName, phaseName); + expect(kubernetesService.getApp).toHaveBeenCalledWith(pipelineName, phaseName, appName, contextName); + expect(result).toBe(app); + }); + + it('should return undefined if context is not found', async () => { + const pipelineName = 'test-pipeline'; + const phaseName = 'test-phase'; + const appName = 'test-app'; + + jest.spyOn(pipelinesService, 'getContext').mockResolvedValue('example-context'); + + const result = await service.getApp(pipelineName, phaseName, appName); + + expect(pipelinesService.getContext).toHaveBeenCalledWith(pipelineName, phaseName); + expect(kubernetesService.getApp).not.toHaveBeenCalled(); + expect(result).toBeUndefined(); + }); +}); diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts new file mode 100644 index 00000000..4496387b --- /dev/null +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -0,0 +1,24 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PipelinesService } from '../pipelines/pipelines.service'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; + +@Injectable() +export class AppsService { + + private Logger = new Logger(AppsService.name); + + constructor( + private kubectl: KubernetesService, + private pipelinesService: PipelinesService + ) {} + + public async getApp(pipelineName: string, phaseName: string, appName: string) { + this.Logger.debug('get App: '+appName+' in '+ pipelineName+' phase: '+phaseName); + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + + if (contextName) { + let app = await this.kubectl.getApp(pipelineName, phaseName, appName, contextName); + return app; + } + } +} diff --git a/server-refactored-v3/src/metrics/metrics.controller.spec.ts b/server-refactored-v3/src/metrics/metrics.controller.spec.ts new file mode 100644 index 00000000..bb34159a --- /dev/null +++ b/server-refactored-v3/src/metrics/metrics.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MetricsController } from './metrics.controller'; + +describe('MetricsController', () => { + let controller: MetricsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [MetricsController], + }).compile(); + + controller = module.get(MetricsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/metrics/metrics.controller.ts b/server-refactored-v3/src/metrics/metrics.controller.ts new file mode 100644 index 00000000..8d25b8ec --- /dev/null +++ b/server-refactored-v3/src/metrics/metrics.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiOperation } from '@nestjs/swagger'; +import { MetricsService } from './metrics.service'; + +@Controller({ path: 'api/metrics', version: '1' }) +export class MetricsController { + constructor( + private metricsService: MetricsService, + ) {} + + @ApiOperation({ summary: 'Get metrics for a specific app' }) + @Get('/:pipeline/:phase/:app') + async getMetrics( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + ) { + return this.metricsService.getPodMetrics(pipeline, phase, app); + } +} diff --git a/server-refactored-v3/src/metrics/metrics.interface.ts b/server-refactored-v3/src/metrics/metrics.interface.ts new file mode 100644 index 00000000..10a8dbd1 --- /dev/null +++ b/server-refactored-v3/src/metrics/metrics.interface.ts @@ -0,0 +1,31 @@ +export interface MetricsOptions { + enabled: boolean, + endpoint: string, +} + +export interface PrometheusQuery { + scale: '2h' | '24h' | '7d', + pipeline: string, + phase: string, + app?: string, + host?: string, + calc?: 'rate' | 'increase' +} +export interface IMetric { + name: string, + metric: any, + data: { + x: Date, + y: number + }[] +} + +export type Rule = { + alert: any, + duration: number, + health: string, + labels: any, + name: string, + query: string, + alerting: boolean, +} \ No newline at end of file diff --git a/server-refactored-v3/src/metrics/metrics.module.ts b/server-refactored-v3/src/metrics/metrics.module.ts index b4e50660..1afeeec2 100644 --- a/server-refactored-v3/src/metrics/metrics.module.ts +++ b/server-refactored-v3/src/metrics/metrics.module.ts @@ -1,4 +1,10 @@ import { Module } from '@nestjs/common'; +import { MetricsController } from './metrics.controller'; +import { MetricsService } from './metrics.service'; +import { KubernetesModule } from '../kubernetes/kubernetes.module'; -@Module({}) +@Module({ + controllers: [MetricsController], + providers: [MetricsService, KubernetesModule] +}) export class MetricsModule {} diff --git a/server-refactored-v3/src/metrics/metrics.service.spec.ts b/server-refactored-v3/src/metrics/metrics.service.spec.ts new file mode 100644 index 00000000..7575fd09 --- /dev/null +++ b/server-refactored-v3/src/metrics/metrics.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MetricsService } from './metrics.service'; + +describe('MetricsService', () => { + let service: MetricsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [MetricsService], + }).compile(); + + service = module.get(MetricsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/metrics/metrics.service.ts b/server-refactored-v3/src/metrics/metrics.service.ts new file mode 100644 index 00000000..dec99c21 --- /dev/null +++ b/server-refactored-v3/src/metrics/metrics.service.ts @@ -0,0 +1,361 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { MetricsOptions, IMetric, PrometheusQuery, Rule } from './metrics.interface'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; +import { PrometheusDriver, PrometheusQueryDate, QueryResult, RuleGroup } from 'prometheus-query'; + +@Injectable() +export class MetricsService { + private prom: PrometheusDriver + private status: boolean = false; + + constructor( + //options: MetricsOptions + private kubectl: KubernetesService + ) { + //TODO: Load options from settings + const options = { + enabled: true, + endpoint: 'http://prometheus.localhost' + } as MetricsOptions; + + this.prom = new PrometheusDriver({ + endpoint: options.endpoint, + preferPost: false, + withCredentials: false, + }); + + if (!options.enabled) { + Logger.log('☑️ Feature: Prometheus Metrics not enabled ...', 'Feature'); + this.status = false; + return + } + + this.prom.status().then((status) => { + Logger.log('✅ Feature: Prometheus Metrics initialized::: '+ options.endpoint, 'Feature'); + this.status = true; + }).catch((error) => { + Logger.log('❌ Feature: Prometheus not accesible ...', 'Feature'); + this.status = false; + }) + + } + + public async getStatus(): Promise { + try { + const status = await this.prom.status(); + + if (status === undefined || status === null || status === false) { + return false; + } else { + return true; + } + } catch (error) { + return false; + } + } + + public async getLongTermMetrics(query: string): Promise { + let result: QueryResult | undefined; + try { + result = await this.prom.instantQuery(query); + } catch (error) { + console.log(error); + console.log("query:", query); + console.log(this.prom); + } + return result + + /* Manual Query + const res = await axios.get('http://prometheus.localhost/api/v1/query', { + params: { + query: query + } + }).catch((error) => { + console.log(error); + }); + if (res === undefined) { + return undefined + } + return res.data.data.result + */ + } + + public async queryMetrics(metric:string, q: PrometheusQuery): Promise { + const query = `${metric}{namespace="${q.pipeline}-${q.phase}", container=~"kuberoapp-web|kuberoapp-worker"}`; + //console.log(query); + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + let result: QueryResult | undefined; + try { + result = await this.prom.rangeQuery(query, start, end, step); + } catch (error) { + console.log(error); + console.log(q); + console.log("query:", query); + console.log(end, start, step ); + console.log(this.prom); + } + return result; + } + + public async getMemoryMetrics(q: PrometheusQuery): Promise { + + let resp = [] as IMetric[]; + let metrics: QueryResult + try { + const res = await this.queryMetrics('container_memory_rss', q); + if (res === undefined) { + throw new Error("no metrics found") + } else { + metrics = res; + } + } catch (error) { + console.log("error fetching load metrics") + throw error + } + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value / 1000000] + }); + resp.push({ + name: metrics.result[i].metric.labels.pod, + metric: metrics.result[i].metric, + data: data + }); + } + + return resp; + } + + public async getLoadMetrics(q: PrometheusQuery): Promise { + let resp = [] as IMetric[]; + let metrics: QueryResult + try { + const res = await this.queryMetrics('container_cpu_load_average_10s', q); + if (res === undefined) { + throw new Error("no metrics found") + } else { + metrics = res; + } + } catch (error) { + console.log("error fetching load metrics") + throw error + } + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value] + }); + resp.push({ + name: metrics.result[i].metric.labels.pod, + metric: metrics.result[i].metric, + data: data + }); + } + + return resp; + } + + private getStepsAndStart(scale: string): { end: Date, start: number, step: number, vector: string} { + const end = new Date(); + let start = new Date().getTime() - 24 * 60 * 60 * 1000 + let step = 60 * 10 + let vector = '5m' + switch (scale) { + case '2h': + start = new Date().getTime() - 2 * 60 * 60 * 1000 + step = 48 // 48 seconds + vector = '5m' + break + case '24h': + start = new Date().getTime() - 24 * 60 * 60 * 1000 + step = 60 * 10 // 10 minutes + vector = '10m' + break + case '7d': + start = new Date().getTime() - 7 * 24 * 60 * 60 * 1000 + step = 60 * 120 // 700 minutes + vector = '20m' + break + } + + return { + end: end, + start: start, + step: step, + vector: vector + } + } + + public async getCPUMetrics(q: PrometheusQuery): Promise { + let resp = [] as IMetric[]; + let metrics: QueryResult + + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // rate(nginx_ingress_controller_requests{namespace="asdf-production", host="a.a.localhost"}[10m]) + const query = `${q.calc}(container_cpu_usage_seconds_total{namespace="${q.pipeline}-${q.phase}", container=~"kuberoapp-web|kuberoapp-worker"}[${vector}])`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value] + }); + resp.push({ + name: metrics.result[i].metric.labels.pod, + metric: metrics.result[i].metric, + data: data + }); + } + } catch (error) { + console.log(error); + console.log(q); + console.log("query:", query); + console.log(end, start, step ); + console.log(this.prom); + } + return resp; + } + + public async getHttpStatusCodesMetrics(q: PrometheusQuery): Promise { + let resp = [] as IMetric[]; + let metrics: QueryResult + + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // rate(nginx_ingress_controller_requests{namespace="asdf-production", host="a.a.localhost"}[10m]) + const query = `${q.calc}(nginx_ingress_controller_requests{namespace="${q.pipeline}-${q.phase}", host="${q.host}"}[${vector}])`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value] + }); + resp.push({ + name: metrics.result[i].metric.labels.status, + metric: metrics.result[i].metric, + data: data + }); + } + } catch (error) { + console.log(error); + console.log(q); + console.log("query:", query); + console.log(end, start, step ); + console.log(this.prom); + } + return resp; + } + + + public async getHttpResponseTimeMetrics(q: PrometheusQuery): Promise { + let resp = [] as IMetric[]; + let metrics: QueryResult + + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // rate(nginx_ingress_controller_response_duration_seconds_count{namespace="asdf-production", host="a.a.localhost",status="200"}[10m]) //in ms + const query = `${q.calc}(nginx_ingress_controller_response_duration_seconds_count{namespace="${q.pipeline}-${q.phase}", host="${q.host}", status="200"}[${vector}])`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value/1000] + }); + resp.push({ + name: metrics.result[i].metric.labels.status, + metric: metrics.result[i].metric, + data: data + }); + } + } catch (error) { + console.log(error); + console.log(q); + console.log("query:", query); + console.log(end, start, step ); + console.log(this.prom); + } + return resp; + } + + public async getHttpResponseTrafficMetrics(q: PrometheusQuery): Promise { + let resp = [] as IMetric[]; + let metrics: QueryResult + + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // sum(rate(nginx_ingress_controller_response_size_sum{namespace="asdf-production", host="a.a.localhost"}[10m])) + const query = `sum(${q.calc}(nginx_ingress_controller_response_size_sum{namespace="${q.pipeline}-${q.phase}", host="${q.host}"}[${vector}]))`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value/1000] + }); + resp.push({ + name: metrics.result[i].metric.labels.status, + metric: metrics.result[i].metric, + data: data + }); + } + } catch (error) { + console.log(error); + console.log(q); + console.log("query:", query); + console.log(end, start, step ); + console.log(this.prom); + } + return resp; + } + + public async getRules(q: {app: string, phase: string, pipeline: string}): Promise { + let rules: RuleGroup[] = []; + try { + rules = await this.prom.rules(); + } catch (error) { + console.log("error fetching rules") + } + + let ruleslist: Rule[] = []; + + // filter for dedicated app + for (let i = 0; i < rules.length; i++) { + for (let j = 0; j < rules[i].rules.length; j++) { + // remove not matching alerts + rules[i].rules[j].alerts = rules[i].rules[j].alerts.filter((a: any) => { + console.log("a.labels.namespace: "+a.labels.namespace+" == q.pipeline: "+q.pipeline+"-"+q.phase) + console.log("a.labels.service: "+a.labels.service+" q.app: "+q.app+"-kuberoapp"); + return a.labels.namespace === q.pipeline+"-"+q.phase && ( + a.labels.service === q.app+"-kuberoapp" || + a.labels.deployment?.startsWith(q.app+"-kuberoapp") || + a.labels.replicaset?.startsWith(q.app+"-kuberoapp") || + a.labels.statefulset === q.app+"-kuberoapp" || + a.labels.daemonset === q.app+"-kuberoapp" || + a.labels.pod === q.app+"-kuberoapp" || + a.labels.container === q.app+"-kuberoapp" || + a.labels.job === q.app+"-kuberoapp" + ) + }); + + let r: Rule = { + alert: rules[i].rules[j].alerts[0], + duration: rules[i].rules[j].duration || 0, + health: rules[i].rules[j].health || '', + labels: rules[i].rules[j].labels || {}, + name: rules[i].rules[j].name || '', + query: rules[i].rules[j].query || '', + alerting: rules[i].rules[j].alerts.length > 0 ? true : false, + }; + + if (rules[i].rules[j].type === 'alerting') { + ruleslist.push(r); + } + } + } + + return ruleslist; + } + + public getPodMetrics(pipelineName: string, phaseName: string, appName: string) { + const namespace = pipelineName+'-'+phaseName; + return this.kubectl.getPodMetrics(namespace, appName); + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/pipelines/pipelines.module.ts b/server-refactored-v3/src/pipelines/pipelines.module.ts index e4317d13..263de029 100644 --- a/server-refactored-v3/src/pipelines/pipelines.module.ts +++ b/server-refactored-v3/src/pipelines/pipelines.module.ts @@ -1,9 +1,11 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { PipelinesController } from './pipelines.controller'; import { PipelinesService } from './pipelines.service'; +@Global() @Module({ controllers: [PipelinesController], - providers: [PipelinesService] + providers: [PipelinesService], + exports: [PipelinesService], }) export class PipelinesModule {} diff --git a/server-refactored-v3/src/pipelines/pipelines.service.ts b/server-refactored-v3/src/pipelines/pipelines.service.ts index 3ac14981..1d21ae63 100644 --- a/server-refactored-v3/src/pipelines/pipelines.service.ts +++ b/server-refactored-v3/src/pipelines/pipelines.service.ts @@ -12,13 +12,11 @@ export class PipelinesService { constructor(private kubectl: KubernetesService) {} public async listPipelines(): Promise { - this.logger.debug('listPipelines'); let pipelines = await this.kubectl.getPipelinesList(); const ret: IPipelineList = { items: new Array() } for (const pipeline of pipelines.items) { - this.logger.debug('listed pipeline: '+pipeline.spec.name); ret.items.push(pipeline.spec); } return ret; diff --git a/server-refactored-v3/src/repo/git/bitbucket.ts b/server-refactored-v3/src/repo/git/bitbucket.ts index 0eccf3b0..d47b7b0f 100644 --- a/server-refactored-v3/src/repo/git/bitbucket.ts +++ b/server-refactored-v3/src/repo/git/bitbucket.ts @@ -5,7 +5,6 @@ import { Repo } from './repo'; import gitUrlParse = require("git-url-parse"); debug('app:kubero:bitbucket:api') -//const { Octokit } = require("@octokit/core"); import { Bitbucket, APIClient } from "bitbucket" import { RequestError } from '@octokit/types'; import { Logger } from '@nestjs/common'; @@ -15,7 +14,8 @@ export class BitbucketApi extends Repo { constructor(username: string, appPassword: string) { super("bitbucket"); - const clientOptions = { + let clientOptions = { + notice: false, auth: { username: username, password: appPassword diff --git a/server-refactored-v3/src/repo/repo.controller.ts b/server-refactored-v3/src/repo/repo.controller.ts index 63780e54..1bd77194 100644 --- a/server-refactored-v3/src/repo/repo.controller.ts +++ b/server-refactored-v3/src/repo/repo.controller.ts @@ -21,4 +21,31 @@ export class RepoController { ) { return this.repoService.listRepositoriesByProvider(provider); } + + @ApiOperation({ summary: 'Get a list of available branches' }) + @Get('/:provider/:gitrepob64/branches') + async listBranches( + @Param('provider') provider: string, + @Param('gitrepob64') gitrepob64: string, + ) { + return this.repoService.listBranches(provider, gitrepob64); + } + + @ApiOperation({ summary: 'Get a list of available Pull requests' }) + @Get('/:provider/:gitrepob64/pullrequests') + async listPullRequests( + @Param('provider') provider: string, + @Param('gitrepob64') gitrepob64: string, + ) { + return this.repoService.listPullrequests(provider, gitrepob64); + } + + @ApiOperation({ summary: 'Get a list of all available references' }) + @Get('/:provider/:gitrepob64/references') + async listReferences( + @Param('provider') provider: string, + @Param('gitrepob64') gitrepob64: string, + ) { + return this.repoService.listReferences(provider, gitrepob64); + } } diff --git a/server-refactored-v3/src/repo/repo.service.ts b/server-refactored-v3/src/repo/repo.service.ts index 935b2d56..f6eb6fb0 100644 --- a/server-refactored-v3/src/repo/repo.service.ts +++ b/server-refactored-v3/src/repo/repo.service.ts @@ -115,7 +115,7 @@ export class RepoService { } } - public async listRepoBranches(repoProvider: string, repoB64: string ): Promise { + public async listBranches(repoProvider: string, repoB64: string ): Promise { //return this.git.listRepoBranches(repo, repoProvider); let branches: Promise = new Promise((resolve, reject) => { resolve([]); @@ -147,7 +147,7 @@ export class RepoService { return branches } - public async listRepoPullrequests(repoProvider: string, repoB64: string ): Promise { + public async listPullrequests(repoProvider: string, repoB64: string ): Promise { //return this.git.listRepoBranches(repo, repoProvider); let pulls: Promise = new Promise((resolve, reject) => { resolve([]); diff --git a/server-refactored-v3/src/security/security.controller.spec.ts b/server-refactored-v3/src/security/security.controller.spec.ts new file mode 100644 index 00000000..2ea86e97 --- /dev/null +++ b/server-refactored-v3/src/security/security.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SecurityController } from './security.controller'; + +describe('SecurityController', () => { + let controller: SecurityController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SecurityController], + }).compile(); + + controller = module.get(SecurityController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/security/security.controller.ts b/server-refactored-v3/src/security/security.controller.ts new file mode 100644 index 00000000..83f0139d --- /dev/null +++ b/server-refactored-v3/src/security/security.controller.ts @@ -0,0 +1,22 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { SecurityService } from './security.service'; +import { ApiOperation } from '@nestjs/swagger'; + +@Controller({ path: 'api/security', version: '1' }) +export class SecurityController { + constructor( + private securityService: SecurityService, + ) {} + + @ApiOperation({ summary: 'Get the scan result for a specific app' }) + @Get(':pipeline/:phase/:app/scan/result') + async getScanResult( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Query('logdetails') logdetails: string, + ) { + return this.securityService.getScanResult(pipeline, phase, app, logdetails === 'true'); + } + +} diff --git a/server-refactored-v3/src/security/security.module.ts b/server-refactored-v3/src/security/security.module.ts new file mode 100644 index 00000000..956258e9 --- /dev/null +++ b/server-refactored-v3/src/security/security.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { SecurityController } from './security.controller'; +import { SecurityService } from './security.service'; +import { KubernetesModule } from '../kubernetes/kubernetes.module'; +import { PipelinesModule } from '../pipelines/pipelines.module'; +import { AppsService } from '../apps/apps.service'; + +@Module({ + controllers: [SecurityController], + providers: [SecurityService, KubernetesModule, PipelinesModule, AppsService] +}) +export class SecurityModule {} diff --git a/server-refactored-v3/src/security/security.service.spec.ts b/server-refactored-v3/src/security/security.service.spec.ts new file mode 100644 index 00000000..17280267 --- /dev/null +++ b/server-refactored-v3/src/security/security.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SecurityService } from './security.service'; + +describe('SecurityService', () => { + let service: SecurityService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SecurityService], + }).compile(); + + service = module.get(SecurityService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/security/security.service.ts b/server-refactored-v3/src/security/security.service.ts new file mode 100644 index 00000000..e1669b1a --- /dev/null +++ b/server-refactored-v3/src/security/security.service.ts @@ -0,0 +1,125 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; +import { IKubectlApp } from '../kubernetes/kubernetes.interface'; +import { PipelinesService } from '../pipelines/pipelines.service'; +import { AppsService } from '../apps/apps.service'; + +@Injectable() +export class SecurityService { + private Logger = new Logger(SecurityService.name); + + constructor( + private kubectl: KubernetesService, + private pipelinesService: PipelinesService, + private appsService: AppsService + ) {} + + public async getScanResult(pipeline: string, phase: string, appName: string, logdetails: boolean) { + const contextName = await this.pipelinesService.getContext(pipeline, phase); + const namespace = pipeline+'-'+phase; + + let scanResult = { + status: 'error', + message: 'unknown error', + deploymentstrategy: '', + pipeline: pipeline, + phase: phase, + app: appName, + namespace: namespace, + logsummary: {}, + logs: {}, + logPod: '' + } + + if (!contextName) { + scanResult.status = 'error' + scanResult.message = 'no context found' + return scanResult; + } + + const appresult = await this.appsService.getApp(pipeline, phase, appName) + + const app = appresult?.body as IKubectlApp; + + const logPod = await this.kubectl.getLatestPodByLabel(namespace, `vulnerabilityscan=${appName}`); + + if (!logPod.name) { + scanResult.status = 'error' + scanResult.message = 'no vulnerability scan pod found' + return scanResult; + } + + let logs = ''; + if (contextName) { + this.kubectl.setCurrentContext(contextName); + logs = await this.kubectl.getVulnerabilityScanLogs(namespace, logPod.name); + } + + if (!logs) { + scanResult.status = 'running' + scanResult.message = 'no vulnerability scan logs found' + return scanResult; + } + + const logsummary = this.getVulnSummary(logs); + + scanResult.status = 'ok' + scanResult.message = 'vulnerability scan result' + scanResult.deploymentstrategy = app?.spec?.deploymentstrategy + scanResult.logsummary = logsummary + scanResult.logPod = logPod + + + if (logdetails) { + scanResult.logs = logs; + } + + return scanResult; + } + + private getVulnSummary(logs: any) { + let summary = { + total: 0, + critical: 0, + high: 0, + medium: 0, + low: 0, + unknown: 0 + } + + if (!logs || !logs.Results) { + this.Logger.error(logs); + this.Logger.error('no logs found or not able to parse results'); + return summary; + } + + logs.Results.forEach((target: any) => { + if (target.Vulnerabilities) { + target.Vulnerabilities.forEach((vuln: any) => { + summary.total++; + switch (vuln.Severity) { + case 'CRITICAL': + summary.critical++; + break; + case 'HIGH': + summary.high++; + break; + case 'MEDIUM': + summary.medium++; + break; + case 'LOW': + summary.low++; + break; + case 'UNKNOWN': + summary.unknown++; + break; + default: + summary.unknown++; + } + }); + } + }); + + return summary; + } +} diff --git a/server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts b/server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts deleted file mode 100644 index a8dd65c9..00000000 --- a/server-refactored-v3/src/vulnerabilities/vulnerabilities.module.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Module } from '@nestjs/common'; - -@Module({}) -export class VulnerabilitiesModule {} diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index befb03f2..552d294d 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -2274,7 +2274,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== -axios@^1.7.9: +axios@^1.6.0, axios@^1.7.9: version "1.7.9" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== @@ -5609,6 +5609,13 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prometheus-query@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/prometheus-query/-/prometheus-query-3.4.1.tgz#1846b7dc26702d5e5fa53d862482b4ddbffa2345" + integrity sha512-OwktfrdZ4m35j7SJXmWq/Dwl9K63g+rNjCy9oUJo5CVsaiTh+hOOryWaSSzthPkgUmFn580/tdM9DUd9KsxjFg== + dependencies: + axios "^1.6.0" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 6bba9885..9cb14ffc 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -1313,6 +1313,7 @@ export class Kubero { }; } + //Migration to security public async getScanResult(pipeline: string, phase: string, appName: string, logdetails: boolean) { const contextName = this.getContext(pipeline, phase); const namespace = pipeline+'-'+phase; @@ -1371,6 +1372,7 @@ export class Kubero { return scanResult; } + //Migration to security private getVulnSummary(logs: any) { let summary = { total: 0, From 5c0580a97a6be2551a5d825b535dfab5ab201e6b Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sun, 9 Feb 2025 23:26:00 +0100 Subject: [PATCH 25/65] security fix Polynomial regular expression --- server-refactored-v3/src/repo/git/repo.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server-refactored-v3/src/repo/git/repo.ts b/server-refactored-v3/src/repo/git/repo.ts index bbcbcc06..dec96448 100644 --- a/server-refactored-v3/src/repo/git/repo.ts +++ b/server-refactored-v3/src/repo/git/repo.ts @@ -119,8 +119,8 @@ export abstract class Repo { } protected parseRepo(gitrepo: string): {owner: string, repo: string} { - let owner = gitrepo.match(/^git@.*:(.*)\/.*$/)?.[1] as string; - let repo = gitrepo.match(/^git@.*:.*\/(.*).git$/)?.[1] as string; + let owner = gitrepo.match(/^git@.{0,100}:(.{0,100})\/.{0,100}$/)?.[1] as string; + let repo = gitrepo.match(/^git@.{0,100}:.{0,100}\/(.{0,100}).git$/)?.[1] as string; return { owner: owner, repo: repo }; } From 6a60f73ef569478ecd11e4fa74c43aaf2cc812ef Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Mon, 10 Feb 2025 16:53:13 +0100 Subject: [PATCH 26/65] migrate logs and deployments --- client/src/components/apps/addons.vue | 2 +- client/src/components/apps/appstats.vue | 4 +- client/src/components/apps/buildsform.vue | 2 +- client/src/components/apps/detail.vue | 2 +- client/src/components/apps/events.vue | 2 +- client/src/components/apps/form.vue | 20 +- client/src/components/pipelines/appcard.vue | 2 +- .../src/components/settings/form-general.vue | 2 +- server-refactored-v3/src/app.module.ts | 2 - .../apps.controller.spec.ts} | 10 +- .../src/apps/apps.controller.ts | 18 ++ server-refactored-v3/src/apps/apps.module.ts | 2 + .../src/apps/apps.service.spec.ts | 3 +- server-refactored-v3/src/apps/apps.service.ts | 1 + .../deployments.controller.spec.ts | 18 ++ .../src/deployments/deployments.controller.ts | 21 ++ .../src/deployments/deployments.interface.ts | 117 ++++++++++ .../src/deployments/deployments.module.ts | 11 +- .../deployments/deployments.service.spec.ts | 18 ++ .../src/deployments/deployments.service.ts | 205 ++++++++++++++++++ .../src/events/events.gateway.ts | 4 + .../kubernetes/kubernetes.controller.spec.ts | 18 ++ .../src/kubernetes/kubernetes.controller.ts | 29 +++ .../src/kubernetes/kubernetes.module.ts | 2 + .../src/kubernetes/kubernetes.service.ts | 28 ++- .../src/logs/logs.controller.spec.ts | 18 ++ .../src/logs/logs.controller.ts | 33 +++ .../src/logs/logs.interface.ts | 12 + server-refactored-v3/src/logs/logs.module.ts | 12 +- .../src/logs/logs.service.spec.ts | 18 ++ server-refactored-v3/src/logs/logs.service.ts | 182 ++++++++++++++++ .../src/metrics/metrics.controller.ts | 11 +- .../src/metrics/metrics.service.ts | 7 +- .../src/security/security.service.ts | 2 +- .../src/settings/settings.controller.ts | 19 +- .../src/settings/settings.service.ts | 57 +++-- .../src/templates/templates.controller.ts | 11 - .../src/templates/templates.module.ts | 7 - server/src/kubero.ts | 9 + 39 files changed, 861 insertions(+), 80 deletions(-) rename server-refactored-v3/src/{templates/templates.controller.spec.ts => apps/apps.controller.spec.ts} (50%) create mode 100644 server-refactored-v3/src/apps/apps.controller.ts create mode 100644 server-refactored-v3/src/deployments/deployments.controller.spec.ts create mode 100644 server-refactored-v3/src/deployments/deployments.controller.ts create mode 100644 server-refactored-v3/src/deployments/deployments.interface.ts create mode 100644 server-refactored-v3/src/deployments/deployments.service.spec.ts create mode 100644 server-refactored-v3/src/deployments/deployments.service.ts create mode 100644 server-refactored-v3/src/kubernetes/kubernetes.controller.spec.ts create mode 100644 server-refactored-v3/src/kubernetes/kubernetes.controller.ts create mode 100644 server-refactored-v3/src/logs/logs.controller.spec.ts create mode 100644 server-refactored-v3/src/logs/logs.controller.ts create mode 100644 server-refactored-v3/src/logs/logs.interface.ts create mode 100644 server-refactored-v3/src/logs/logs.service.spec.ts create mode 100644 server-refactored-v3/src/logs/logs.service.ts delete mode 100644 server-refactored-v3/src/templates/templates.controller.ts delete mode 100644 server-refactored-v3/src/templates/templates.module.ts diff --git a/client/src/components/apps/addons.vue b/client/src/components/apps/addons.vue index 70e20ad4..4fc220b2 100644 --- a/client/src/components/apps/addons.vue +++ b/client/src/components/apps/addons.vue @@ -272,7 +272,7 @@ export default defineComponent({ this.dialog = true; }, loadStorageClasses() { - axios.get(`/api/config/storageclasses`) + axios.get(`/api/kubernetes/storageclasses`) .then(response => { for (let storageClass of response.data) { this.availableStorageClasses.push({ diff --git a/client/src/components/apps/appstats.vue b/client/src/components/apps/appstats.vue index e8d2bfea..3def6891 100644 --- a/client/src/components/apps/appstats.vue +++ b/client/src/components/apps/appstats.vue @@ -480,7 +480,7 @@ export default defineComponent({ }, methods: { loadUptimes() { - axios.get(`/api/uptimes/${this.pipeline}/${this.phase}`) + axios.get(`/api/metrics/uptimes/${this.pipeline}/${this.phase}`) .then(response => { this.uptimes = response.data; this.loadMetrics(); @@ -491,7 +491,7 @@ export default defineComponent({ }, loadMetrics() { - axios.get(`/api/metrics/${this.pipeline}/${this.phase}/${this.app}`) + axios.get(`/api/metrics/resources/${this.pipeline}/${this.phase}/${this.app}`) .then(response => { for (var i = 0; i < response.data.length; i++) { if (response.data[i].cpu.percentage != null && response.data[i].memory.percentage != null) { diff --git a/client/src/components/apps/buildsform.vue b/client/src/components/apps/buildsform.vue index 380741dc..018a8fff 100644 --- a/client/src/components/apps/buildsform.vue +++ b/client/src/components/apps/buildsform.vue @@ -137,7 +137,7 @@ export default defineComponent({ const repoB64 = btoa(this.appData?.spec.gitrepo.ssh_url) //const provider = this.appData?.spec.gitrepo.provider const provider = "github" // TODO: FIX: get provider from appData - axios.get(`/api/repo/${provider}/${repoB64}/references/list`) + axios.get(`/api/repo/${provider}/${repoB64}/references`) .then(response => { this.references = response.data }) diff --git a/client/src/components/apps/detail.vue b/client/src/components/apps/detail.vue index 2df38c49..475623ee 100644 --- a/client/src/components/apps/detail.vue +++ b/client/src/components/apps/detail.vue @@ -160,7 +160,7 @@ export default defineComponent({ }); }, loadApp() { - axios.get('/api/pipelines/'+this.pipeline+'/'+this.phase+'/'+this.app).then(response => { + axios.get('/api/apps/'+this.pipeline+'/'+this.phase+'/'+this.app).then(response => { this.appData = response.data; //console.log(this.appData); }); diff --git a/client/src/components/apps/events.vue b/client/src/components/apps/events.vue index 67998405..9d51b6dd 100644 --- a/client/src/components/apps/events.vue +++ b/client/src/components/apps/events.vue @@ -139,7 +139,7 @@ export default defineComponent({ const namespace = this.pipeline + "-" + this.phase; //axios.get(`/api/events?namespace=${this.$route.query.namespace}`) //console.log("loadEvents", namespace); - axios.get(`/api/events?namespace=${namespace}`) + axios.get(`/api/kubernetes/events?namespace=${namespace}`) .then(response => { // sort by creationTimestamp response.data.sort((a: any, b: any) => { diff --git a/client/src/components/apps/form.vue b/client/src/components/apps/form.vue index 11a00817..41403958 100644 --- a/client/src/components/apps/form.vue +++ b/client/src/components/apps/form.vue @@ -1874,7 +1874,7 @@ export default defineComponent({ return domainsList; }, getDomains() { - axios.get('/api/domains').then(response => { + axios.get('/api/kubernetes/domains').then(response => { this.takenDomains = this.whiteListDomains(response.data); }); }, @@ -1893,8 +1893,8 @@ export default defineComponent({ } }, loadClusterIssuers(){ - axios.get('/api/config/clusterissuers').then(response => { - this.letsecryptClusterIssuer = response.data.id; + axios.get('/api/settings/clusterissuer').then(response => { + this.letsecryptClusterIssuer = response.data.clusterissuer; }); }, loadTemplate(template: string) { @@ -2029,7 +2029,7 @@ export default defineComponent({ }); }, loadStorageClasses() { - axios.get('/api/config/storageclasses').then(response => { + axios.get('/api/kubernetes/storageclasses').then(response => { for (let i = 0; i < response.data.length; i++) { this.storageclasses.push(response.data[i].name); } @@ -2046,7 +2046,7 @@ export default defineComponent({ const gitrepoB64 = btoa(this.pipelineData.git.repository.ssh_url); const gitprovider = this.pipelineData.git.provider; - axios.get('/api/repo/'+gitprovider+"/"+gitrepoB64+"/branches/list").then(response => { + axios.get('/api/repo/'+gitprovider+"/"+gitrepoB64+"/branches").then(response => { if (response.data.length === 0) { return; } @@ -2067,7 +2067,7 @@ export default defineComponent({ loadPodsizeList() { - axios.get('/api/config/podsize').then(response => { + axios.get('/api/settings/podsizes').then(response => { if (response.data.length > 0 && this.app == 'new') { this.podsize = response.data[0]; } @@ -2086,7 +2086,7 @@ export default defineComponent({ }, loadBuildpacks() { - axios.get('/api/config/buildpacks').then(response => { + axios.get('/api/settings/buildpacks').then(response => { for (let i = 0; i < response.data.length; i++) { this.buildpacks.push({ text: response.data[i].name, @@ -2101,7 +2101,7 @@ export default defineComponent({ }, deleteApp() { - axios.delete(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`) + axios.delete(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`) .then(() => { // wait for 1 second and redirect to apps page // this avoids a race condition with the backend @@ -2115,7 +2115,7 @@ export default defineComponent({ }, loadApp() { if (this.app !== 'new') { - axios.get(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`).then(response => { + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`).then(response => { this.resourceVersion = response.data.resourceVersion; // Open Panel if there is some data to show @@ -2325,7 +2325,7 @@ export default defineComponent({ postdata.image.run.securityContext.runAsGroup = parseInt(postdata.image.run.securityContext.runAsGroup); } - axios.put(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`, postdata + axios.put(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`, postdata // eslint-disable-next-line no-unused-vars ).then(response => { this.$router.push(`/pipeline/${this.pipeline}/apps`); diff --git a/client/src/components/pipelines/appcard.vue b/client/src/components/pipelines/appcard.vue index d10e252b..0d50c5d3 100644 --- a/client/src/components/pipelines/appcard.vue +++ b/client/src/components/pipelines/appcard.vue @@ -283,7 +283,7 @@ export default defineComponent({ this.loadingState = false; }, loadMetrics() { - axios.get(`/api/metrics/${this.pipeline}/${this.phase}/${this.app.name}`) + axios.get(`/api/metrics/resources/${this.pipeline}/${this.phase}/${this.app.name}`) .then(response => { for (var i = 0; i < response.data.length; i++) { if (response.data[i].cpu.percentage != null && response.data[i].memory.percentage != null) { diff --git a/client/src/components/settings/form-general.vue b/client/src/components/settings/form-general.vue index ea6b6984..81fef60a 100644 --- a/client/src/components/settings/form-general.vue +++ b/client/src/components/settings/form-general.vue @@ -380,7 +380,7 @@ export default defineComponent({ }, methods: { loadStorageClasses() { - axios.get('/api/config/storageclasses').then(response => { + axios.get('/api/kubernetes/storageclasses').then(response => { for (let i = 0; i < response.data.length; i++) { this.storageclasses.push(response.data[i].name); } diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index ac1ee70c..f7eaa333 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -10,7 +10,6 @@ import { PipelinesModule } from './pipelines/pipelines.module'; import { ConfigModule } from './config/config.module'; import { RepoModule } from './repo/repo.module'; import { SettingsModule } from './settings/settings.module'; -import { TemplatesModule } from './templates/templates.module'; import { MetricsModule } from './metrics/metrics.module'; import { LogsModule } from './logs/logs.module'; import { DeploymentsModule } from './deployments/deployments.module'; @@ -35,7 +34,6 @@ import { SecurityModule } from './security/security.module'; ConfigModule, RepoModule, SettingsModule, - TemplatesModule, MetricsModule, LogsModule, DeploymentsModule, diff --git a/server-refactored-v3/src/templates/templates.controller.spec.ts b/server-refactored-v3/src/apps/apps.controller.spec.ts similarity index 50% rename from server-refactored-v3/src/templates/templates.controller.spec.ts rename to server-refactored-v3/src/apps/apps.controller.spec.ts index 7017523b..47b90f30 100644 --- a/server-refactored-v3/src/templates/templates.controller.spec.ts +++ b/server-refactored-v3/src/apps/apps.controller.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { TemplatesController } from './templates.controller'; +import { AppsController } from './apps.controller'; -describe('TemplatesController', () => { - let controller: TemplatesController; +describe('AppsController', () => { + let controller: AppsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [TemplatesController], + controllers: [AppsController], }).compile(); - controller = module.get(TemplatesController); + controller = module.get(AppsController); }); it('should be defined', () => { diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts new file mode 100644 index 00000000..360b8f53 --- /dev/null +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Get, HttpCode, HttpStatus, Param } from '@nestjs/common'; +import { AppsService } from './apps.service'; + +@Controller({ path: 'api/apps', version: '1' }) +export class AppsController { + constructor( + private readonly appsService: AppsService, + ) {} + + @Get('/:pipeline/:phase/:app') + async getApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') appName: string, + ) { + return this.appsService.getApp(pipeline, phase, appName); + } +} diff --git a/server-refactored-v3/src/apps/apps.module.ts b/server-refactored-v3/src/apps/apps.module.ts index 1e8d579a..06a7cb42 100644 --- a/server-refactored-v3/src/apps/apps.module.ts +++ b/server-refactored-v3/src/apps/apps.module.ts @@ -2,9 +2,11 @@ import { Module } from '@nestjs/common'; import { AppsService } from './apps.service'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; import { PipelinesService } from '../pipelines/pipelines.service'; +import { AppsController } from './apps.controller'; @Module({ providers: [AppsService, KubernetesModule, PipelinesService], exports: [AppsService], + controllers: [AppsController], }) export class AppsModule {} diff --git a/server-refactored-v3/src/apps/apps.service.spec.ts b/server-refactored-v3/src/apps/apps.service.spec.ts index 6a78ffb3..ff4c0a34 100644 --- a/server-refactored-v3/src/apps/apps.service.spec.ts +++ b/server-refactored-v3/src/apps/apps.service.spec.ts @@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppsService } from './apps.service'; import { PipelinesService } from '../pipelines/pipelines.service'; import { KubernetesService } from '../kubernetes/kubernetes.service'; +import { IKubectlApp } from 'src/kubernetes/kubernetes.interface'; describe('AppsService', () => { let service: AppsService; @@ -41,7 +42,7 @@ describe('AppsService', () => { const phaseName = 'test-phase'; const appName = 'test-app'; const contextName = 'test-context'; - const app = { response: { statusCode: 200 } as any, body: { name: appName } }; + const app = {} as IKubectlApp; jest.spyOn(pipelinesService, 'getContext').mockResolvedValue(contextName); jest.spyOn(kubernetesService, 'getApp').mockResolvedValue(app); diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index 4496387b..f75646bc 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { PipelinesService } from '../pipelines/pipelines.service'; import { KubernetesService } from '../kubernetes/kubernetes.service'; + @Injectable() export class AppsService { diff --git a/server-refactored-v3/src/deployments/deployments.controller.spec.ts b/server-refactored-v3/src/deployments/deployments.controller.spec.ts new file mode 100644 index 00000000..d0b088d2 --- /dev/null +++ b/server-refactored-v3/src/deployments/deployments.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DeploymentsController } from './deployments.controller'; + +describe('DeploymentsController', () => { + let controller: DeploymentsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [DeploymentsController], + }).compile(); + + controller = module.get(DeploymentsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/deployments/deployments.controller.ts b/server-refactored-v3/src/deployments/deployments.controller.ts new file mode 100644 index 00000000..dc01c40a --- /dev/null +++ b/server-refactored-v3/src/deployments/deployments.controller.ts @@ -0,0 +1,21 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { DeploymentsService } from './deployments.service'; +import { ApiOperation } from '@nestjs/swagger'; + +@Controller({ path: 'api/deployments', version: '1' }) +export class DeploymentsController { + constructor( + private readonly deploymentsService: DeploymentsService + ) {} + + @ApiOperation({ summary: 'List deployments for a specific app' }) + @Get('/:pipeline/:phase/:app') + async getDeployments( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string + ) { + return this.deploymentsService.listBuildjobs(pipeline, phase, app); + } + +} diff --git a/server-refactored-v3/src/deployments/deployments.interface.ts b/server-refactored-v3/src/deployments/deployments.interface.ts new file mode 100644 index 00000000..b62cabdd --- /dev/null +++ b/server-refactored-v3/src/deployments/deployments.interface.ts @@ -0,0 +1,117 @@ + +export type IKuberoBuildjob = { + creationTimestamp: string, + name: string, + app: string, + pipeline: string, + phase: string, //Missing + image: string, + tag: string, + gitrepo: string, + gitref: string, + buildstrategy: string, + + backoffLimit: number, + state: string, + duration: number, + status: { + completionTime?: string, + conditions: Array<{ + lastProbeTime: string + lastTransitionTime: string + message: string + reason: string + status: string + type: string + }> + failed?: number + succeeded?: number + active?: number + ready: number + startTime: string + terminating: number + uncountedTerminatedPods: any + } +} + +export type KuberoBuild = { + apiVersion: string + kind: string + metadata: { + creationTimestamp?: string + finalizers?: Array + generation?: number + managedFields?: Array + name: string + namespace: string + resourceVersion?: string + uid?: string + } + spec: { + app: string, + pipeline: string + id: string, + buildstrategy: string + buildpack?: { + path: string + cnbPlatformApi: string + } + dockerfile?: { + path: string + } + nixpack?: { + path: string + } + git: { + revision?: string //TODO: Remove + ref?: string + url: string + } + podSecurityContext?: { + fsGroup: number + } + repository: { + image: string + tag: string, + active?: boolean + } + } + status?: { + conditions: Array<{ + lastTransitionTime: string + status: string + type: string + reason?: string + }> + deployedRelease?: { + manifest: string + name: string + } + } + jobstatus?: { + duration?: number // in miliseconds + startTime: string + completionTime?: string + status: "Unknown" | "Active" | "Succeeded" | "Failed" + } + } + + + export type KuberoBuildList = { + apiVersion: string + items: Array + kind: string + metadata: { + continue: string + resourceVersion: string + } + } + +/* +export interface DeploymentOptions { + kubectl: Kubectl; + notifications: Notifications; + io: any; + kubero: Kubero; +} +*/ \ No newline at end of file diff --git a/server-refactored-v3/src/deployments/deployments.module.ts b/server-refactored-v3/src/deployments/deployments.module.ts index 98cc80af..01e45972 100644 --- a/server-refactored-v3/src/deployments/deployments.module.ts +++ b/server-refactored-v3/src/deployments/deployments.module.ts @@ -1,4 +1,13 @@ import { Module } from '@nestjs/common'; +import { DeploymentsController } from './deployments.controller'; +import { DeploymentsService } from './deployments.service'; +import { AppsService } from 'src/apps/apps.service'; +import { EventsGateway } from 'src/events/events.gateway'; +import { NotificationsService } from 'src/notifications/notifications.service'; +import { LogsService } from 'src/logs/logs.service'; -@Module({}) +@Module({ + controllers: [DeploymentsController], + providers: [DeploymentsService, AppsService, EventsGateway, NotificationsService, LogsService] +}) export class DeploymentsModule {} diff --git a/server-refactored-v3/src/deployments/deployments.service.spec.ts b/server-refactored-v3/src/deployments/deployments.service.spec.ts new file mode 100644 index 00000000..11b3fb26 --- /dev/null +++ b/server-refactored-v3/src/deployments/deployments.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DeploymentsService } from './deployments.service'; + +describe('DeploymentsService', () => { + let service: DeploymentsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [DeploymentsService], + }).compile(); + + service = module.get(DeploymentsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/deployments/deployments.service.ts b/server-refactored-v3/src/deployments/deployments.service.ts new file mode 100644 index 00000000..62f8854a --- /dev/null +++ b/server-refactored-v3/src/deployments/deployments.service.ts @@ -0,0 +1,205 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { IKuberoBuildjob } from './deployments.interface'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; +import { IKubectlApp } from '../kubernetes/kubernetes.interface'; +import { NotificationsService } from '../notifications/notifications.service'; +import { INotification } from '../notifications/notifications.interface'; +import { IUser } from '../auth/auth.interface'; +import { AppsService } from '../apps/apps.service'; +import { V1JobList } from '@kubernetes/client-node'; +import { PipelinesService } from '../pipelines/pipelines.service'; +import { ILoglines } from '../logs/logs.interface'; +import { LogsService } from '../logs/logs.service'; + +@Injectable() +export class DeploymentsService { + //private _io: any; + //private notification: Notifications; + //private kubero: Kubero; + + constructor( + //options: DeploymentOptions + private kubectl: KubernetesService, + private appsService: AppsService, + private notificationService: NotificationsService, + private pipelinesService: PipelinesService, + private LogsService: LogsService + ) { + //this.kubectl = options.kubectl + //this._io = options.io + //this.notification = options.notifications + //this.kubero = options.kubero + } + + private logger = new Logger(DeploymentsService.name); + + public async listBuildjobs(pipelineName: string, phaseName: string, appName: string): Promise { + const namespace = pipelineName + "-" + phaseName + let jobs = await this.kubectl.getJobs(namespace) as V1JobList + const appresult = await this.appsService.getApp(pipelineName, phaseName, appName) + + const app = appresult as IKubectlApp; + + if (!jobs) { + this.logger.log('No deployments found') + return { + items: [] + } + } + + let retJobs = [] as IKuberoBuildjob[] + for (let j of jobs.items as any) { + + // skip non matching apps + if (j.metadata.labels.kuberoapp != appName) { + continue + } + + const retJob = {} as IKuberoBuildjob + retJob.creationTimestamp = j.metadata.creationTimestamp + retJob.name = j.metadata.name + retJob.app = j.metadata.labels.kuberoapp + retJob.pipeline = j.metadata.labels.kuberopipeline + retJob.phase = j.metadata.labels.kuberophase || '' + retJob.buildstrategy = j.metadata.labels.buildstrategy + retJob.gitrepo = j.spec.template.spec.initContainers[0].env.find((e: any) => e.name == 'GIT_REPOSITORY').value + retJob.gitref = j.spec.template.spec.initContainers[0].env.find((e: any) => e.name == 'GIT_REF').value + retJob.image = j.spec.template.spec.containers[0].env.find((e: any) => e.name == 'REPOSITORY').value + retJob.tag = j.spec.template.spec.containers[0].env.find((e: any) => e.name == 'TAG').value + retJob.backoffLimit = j.spec.backoffLimit + retJob.status = j.status + + if (j.status.failed) { + retJob.state = 'Failed' + retJob.duration = ( new Date(j.status.conditions[0].lastProbeTime).getTime() - new Date(j.status.startTime).getTime() ) + } + if (j.status.active) { + retJob.state = 'Active' + retJob.duration = ( new Date().getTime() - new Date(j.status.startTime).getTime() ) + } + if (j.status.succeeded) { + retJob.state = 'Succeeded' + retJob.duration = ( new Date(j.status.completionTime).getTime() - new Date(j.status.startTime).getTime() ) + } + + retJobs.push(retJob) + } + + return retJobs.reverse() + } + + public async triggerBuildjob( + pipeline: string, + phase: string, + app: string, + buildstrategy: 'buildpacks' | 'dockerfile' | 'nixpacks' | 'plain', + gitrepo: string, + reference: string, + dockerfilePath: string, + user: IUser + ): Promise { + + if ( process.env.KUBERO_READONLY == 'true'){ + this.logger.log('KUBERO_READONLY is set to true, not triggering build for app: '+app + ' in pipeline: '+pipeline); + return; + } + + const namespace = pipeline + "-" + phase + + if ( process.env.KUBERO_READONLY == 'true'){ + this.logger.log('KUBERO_READONLY is set to true'); + return; + } + + // Create the Pipeline CRD + try { + await this.kubectl.createBuildJob( + namespace, + app, + pipeline, + buildstrategy, + dockerfilePath, + { + ref: reference, + url: gitrepo + }, + { + image: process.env.KUBERO_BUILD_REGISTRY + "/" + pipeline + "/" + app, + tag: reference + } + ) + } catch (error) { + this.logger.error('kubectl.createBuildJob: Error creating Kubero build job', error) + } + + const m = { + 'name': 'newBuild', + 'user': user.username, + 'resource': 'pipeline', + 'action': 'created', + 'severity': 'normal', + 'message': 'Created new Build Job: '+app + ' in pipeline: '+pipeline, + 'pipelineName':pipeline, + 'phaseName': '', + 'appName': '', + 'data': { + 'pipeline': pipeline + } + } as INotification; + this.notificationService.send(m); + + return { + message: 'Build started' + } + } + + public async deleteBuildjob(pipeline: string, phase: string, app: string, buildName: string, user: IUser): Promise { + + if ( process.env.KUBERO_READONLY == 'true'){ + this.logger.log('KUBERO_READONLY is set to true, not creating app: '+app + ' in pipeline: '+pipeline); + return; + } + + const namespace = pipeline + "-" + phase + await this.kubectl.deleteKuberoBuildJob(namespace, buildName) + + const m = { + 'name': 'newBuild', + 'user': user.username, + 'resource': 'build', + 'action': 'deleted', + 'severity': 'normal', + 'message': 'Deleted Build Job: '+app + ' in pipeline: '+pipeline, + 'pipelineName':pipeline, + 'phaseName': '', + 'appName': '', + 'data': { + 'pipeline': pipeline + } + } as INotification; + this.notificationService.send(m); + + return { + message: 'Deployment deleted' + } + } + + public async getBuildLogs(pipelineName: string, phaseName: string, appName: string, buildName: string, containerName: string): Promise { + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + const namespace = pipelineName+'-'+phaseName; + + let loglines = [] as ILoglines[]; + + if (contextName) { + const pods = await this.kubectl.getPods(namespace, contextName); + for (const pod of pods) { + //this.logger.log('Fetching logs for pod: ', pod.metadata?.labels?.["job-name"], buildName) + if (pod.metadata?.labels?.kuberoapp == appName && pod.metadata.name && pod.metadata?.labels?.["job-name"] == buildName) { + const ll = await this.LogsService.fetchLogs(namespace, pod.metadata.name, containerName, pipelineName, phaseName, appName) + loglines = loglines.concat(ll); + } + } + } + return loglines; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/events/events.gateway.ts b/server-refactored-v3/src/events/events.gateway.ts index 85a37789..2c7ab015 100644 --- a/server-refactored-v3/src/events/events.gateway.ts +++ b/server-refactored-v3/src/events/events.gateway.ts @@ -28,4 +28,8 @@ export class EventsGateway { sendEvent(event: string, data: any) { this.server.emit(event, data); } + + sendLogline(room: string, logline: any) { //TODO define logline type + this.server.to(room).emit('log', logline); + } } \ No newline at end of file diff --git a/server-refactored-v3/src/kubernetes/kubernetes.controller.spec.ts b/server-refactored-v3/src/kubernetes/kubernetes.controller.spec.ts new file mode 100644 index 00000000..a9c92a75 --- /dev/null +++ b/server-refactored-v3/src/kubernetes/kubernetes.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { KubernetesController } from './kubernetes.controller'; + +describe('KubernetesController', () => { + let controller: KubernetesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [KubernetesController], + }).compile(); + + controller = module.get(KubernetesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts new file mode 100644 index 00000000..91e14b70 --- /dev/null +++ b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { KubernetesService } from './kubernetes.service'; +import { ApiOperation } from '@nestjs/swagger'; + +@Controller({ path: 'api/kubernetes', version: '1' }) +export class KubernetesController { + constructor( + private readonly kubernetesService: KubernetesService + ) {} + + @ApiOperation({ summary: 'Get the Kubernetes events in a specific namespace' }) + @Get('events') + async getEvents(@Query('namespace') namespace: string) { + return this.kubernetesService.getEvents(namespace); + } + + @ApiOperation({ summary: 'Get the available storage classes' }) + @Get('storageclasses') + async getStorageClasses() { + return this.kubernetesService.getStorageClasses(); + } + + @ApiOperation({ summary: 'Get a list of allredy taken domains on this Kubernets cluster' }) + @Get('domains') + async getDomains() { + return this.kubernetesService.getDomains(); + } + +} diff --git a/server-refactored-v3/src/kubernetes/kubernetes.module.ts b/server-refactored-v3/src/kubernetes/kubernetes.module.ts index 1310061c..368e6ac2 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.module.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.module.ts @@ -1,9 +1,11 @@ import { Global, Module } from '@nestjs/common'; import { KubernetesService } from './kubernetes.service'; +import { KubernetesController } from './kubernetes.controller'; @Global() @Module({ providers: [KubernetesService], exports: [KubernetesService], + controllers: [KubernetesController], }) export class KubernetesModule {} diff --git a/server-refactored-v3/src/kubernetes/kubernetes.service.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts index ba4e40bf..a3a16200 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.service.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -1,5 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList} from './kubernetes.interface'; +import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList, IKubectlApp} from './kubernetes.interface'; import { IPipeline, } from '../pipelines/pipelines.interface'; import { KubectlPipeline } from '../pipelines/pipeline/pipeline'; import { KubectlApp, App } from '../apps/app/app'; @@ -34,6 +34,7 @@ import { import { WebSocket } from 'ws'; import stream from 'stream'; import internal from 'stream'; +import { IKuberoConfig, IKuberoCRD } from 'src/settings/settings.interface'; @Injectable() export class KubernetesService { @@ -341,7 +342,7 @@ export class KubernetesService { }) } - public async getApp(pipelineName: string, phaseName: string, appName: string, context: string) { + public async getApp(pipelineName: string, phaseName: string, appName: string, context: string): Promise { let namespace = pipelineName+'-'+phaseName; this.kc.setCurrentContext(context); @@ -356,7 +357,11 @@ export class KubernetesService { this.logger.debug(error); }) - return app; + if (app) { + return app.body as IKubectlApp; + } else { + return {} as IKubectlApp; + } } public async getAppsList(namespace: string, context: string): Promise { @@ -716,7 +721,7 @@ export class KubernetesService { } - public async getStorageglasses(): Promise { + public async getStorageClasses(): Promise { let ret: { name: string | undefined; provisioner: string; reclaimPolicy: string | undefined; volumeBindingMode: string | undefined; }[] = []; try { const storageClasses = await this.storageV1Api.listStorageClass(); @@ -1000,6 +1005,17 @@ export class KubernetesService { return ingresses.body.items; } + public async getDomains(): Promise { + let allIngress = await this.getAllIngress() + let domains: string[] = [] + allIngress.forEach((ingress: any) => { + ingress.spec.rules.forEach((rule: any) => { + domains.push(rule.host) + }) + }) + return domains + } + public async execInContainer(namespace: string, podName: string, containerName: string, command: string, stdin: internal.PassThrough): Promise { //const command = ['ls', '-al', '.'] //const command = ['bash'] @@ -1017,7 +1033,7 @@ export class KubernetesService { return ws } - public async getKuberoConfig(namespace: string): Promise { + public async getKuberoConfig(namespace: string): Promise { try { const config = await this.customObjectsApi.getNamespacedCustomObject( 'application.kubero.dev', @@ -1027,7 +1043,7 @@ export class KubernetesService { 'kubero' ) //console.log(config.body); - return config.body; + return config.body as any; } catch (error) { //this.logger.debug(error); this.logger.debug("getKuberoConfig: error getting config"); diff --git a/server-refactored-v3/src/logs/logs.controller.spec.ts b/server-refactored-v3/src/logs/logs.controller.spec.ts new file mode 100644 index 00000000..d12acbad --- /dev/null +++ b/server-refactored-v3/src/logs/logs.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LogsController } from './logs.controller'; + +describe('LogsController', () => { + let controller: LogsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [LogsController], + }).compile(); + + controller = module.get(LogsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/logs/logs.controller.ts b/server-refactored-v3/src/logs/logs.controller.ts new file mode 100644 index 00000000..df16b8a2 --- /dev/null +++ b/server-refactored-v3/src/logs/logs.controller.ts @@ -0,0 +1,33 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiOperation } from '@nestjs/swagger'; +import { LogsService } from './logs.service'; + +@Controller({ path: 'api/logs', version: '1' }) +export class LogsController { + + constructor( + private readonly logsService: LogsService + ) {} + + @ApiOperation({ summary: 'Get the logs for a specific container' }) + @Get('/:pipeline/:phase/:app/:container/history') + async getLogs( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Param('container') container: string + ) { + return this.logsService.getLogsHistory(pipeline, phase, app, container); + } + + @ApiOperation({ summary: 'Get the logs for a specific container' }) + @Get('/:pipeline/:phase/:app/') + async getLogsForApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string + ) { + return this.logsService.startLogging(pipeline, phase, app); + } + +} diff --git a/server-refactored-v3/src/logs/logs.interface.ts b/server-refactored-v3/src/logs/logs.interface.ts new file mode 100644 index 00000000..5a42a4a0 --- /dev/null +++ b/server-refactored-v3/src/logs/logs.interface.ts @@ -0,0 +1,12 @@ +export interface ILoglines { + id: string, + time: number, + pipeline: string, + phase: string, + app: string, + pod: string, + podID: string, + container: string, + color: string, + log: string, +} \ No newline at end of file diff --git a/server-refactored-v3/src/logs/logs.module.ts b/server-refactored-v3/src/logs/logs.module.ts index 956d8d10..c53a2326 100644 --- a/server-refactored-v3/src/logs/logs.module.ts +++ b/server-refactored-v3/src/logs/logs.module.ts @@ -1,4 +1,14 @@ import { Module } from '@nestjs/common'; +import { LogsService } from './logs.service'; +import { EventsGateway } from '../events/events.gateway'; +import { NotificationsService } from 'src/notifications/notifications.service'; +import { NotificationsModule } from 'src/notifications/notifications.module'; +import { EventsModule } from 'src/events/events.module'; +import { AppsService } from 'src/apps/apps.service'; +import { LogsController } from './logs.controller'; -@Module({}) +@Module({ + providers: [LogsService, EventsGateway, NotificationsService, AppsService], + controllers: [LogsController] +}) export class LogsModule {} diff --git a/server-refactored-v3/src/logs/logs.service.spec.ts b/server-refactored-v3/src/logs/logs.service.spec.ts new file mode 100644 index 00000000..bad725a2 --- /dev/null +++ b/server-refactored-v3/src/logs/logs.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LogsService } from './logs.service'; + +describe('LogsService', () => { + let service: LogsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [LogsService], + }).compile(); + + service = module.get(LogsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/logs/logs.service.ts b/server-refactored-v3/src/logs/logs.service.ts new file mode 100644 index 00000000..766206ac --- /dev/null +++ b/server-refactored-v3/src/logs/logs.service.ts @@ -0,0 +1,182 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ILoglines } from './logs.interface'; +import { KubernetesService } from '../kubernetes/kubernetes.service'; +import { PipelinesService } from '../pipelines/pipelines.service'; +import { EventsGateway } from 'src/events/events.gateway'; +import { Stream } from 'stream'; +import { v4 as uuidv4 } from 'uuid'; + +@Injectable() +export class LogsService { + private logger = new Logger(LogsService.name); + private podLogStreams: string[]= [] + + constructor( + private kubectl: KubernetesService, + private pipelinesService: PipelinesService, + private EventsGateway: EventsGateway + ) {} + + private logcolor(str: string) { + let hash = 0; + for (var i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let color = '#'; + for (var i = 0; i < 3; i++) { + var value = (hash >> (i * 8)) & 0xFF; + color += ('00' + value.toString(16)).substring(2); + } + return color; + } + + public async emitLogs(pipelineName: string, phaseName: string, appName: string, podName: string, container: string) { + + const logStream = new Stream.PassThrough(); + + logStream.on('data', (chunk: any) => { + // use write rather than console.log to prevent double line feed + //process.stdout.write(chunk); + const roomname = `${pipelineName}-${phaseName}-${appName}`; + const logline = { + id: uuidv4(), + time: new Date().getTime(), + pipeline: pipelineName, + phase: phaseName, + app: appName, + pod: podName, + podID: podName.split('-')[3]+'-'+podName.split('-')[4], + container: container, + color: this.logcolor(podName), + log: chunk.toString() + }; + this.EventsGateway.sendLogline(roomname, logline); + }); + + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + const namespace = pipelineName+'-'+phaseName; + + if (contextName) { + this.kubectl.setCurrentContext(contextName); + + if (!this.podLogStreams.includes(podName)) { + + this.kubectl.log.log(namespace, podName, container, logStream, {follow: true, tailLines: 0, pretty: false, timestamps: false}) + .then(res => { + this.logger.debug('logs started for '+podName+' '+container); + this.podLogStreams.push(podName); + }) + .catch(err => { + this.logger.debug(err); + }); + } else { + this.logger.debug('logs already running '+podName+' '+container); + } + } + } + + public async startLogging(pipelineName: string, phaseName: string, appName: string) { + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + const namespace = pipelineName+'-'+phaseName; + + if (contextName) { + this.kubectl.getPods(namespace, contextName).then((pods: any[]) => { + for (const pod of pods) { + + if (pod.metadata.name.startsWith(appName)) { + for (const container of pod.spec.containers) { + this.emitLogs(pipelineName, phaseName, appName, pod.metadata.name, container.name); + } + /* TODO needs some improvements since it wont load web anymore + for (const initcontainer of pod.spec.initContainers) { + this.emitLogs(pipelineName, phaseName, appName, pod.metadata.name, initcontainer.name); + } + */ + } + } + }); + } + } + + + public async getLogsHistory(pipelineName: string, phaseName: string, appName: string, container: string) { + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + const namespace = pipelineName+'-'+phaseName; + + let loglines: ILoglines[] = []; + if (contextName) { + const pods = await this.kubectl.getPods(namespace, contextName); + for (const pod of pods) { + + if (pod.metadata?.name?.startsWith(appName)) { + if (container == 'web') { + for (const container of pod.spec?.containers || []) { + // only fetch logs for the web container, exclude trivy and build jobs + if (!pod.metadata?.labels?.["job-name"]) { + const ll = await this.fetchLogs(namespace, pod.metadata.name, container.name, pipelineName, phaseName, appName) + loglines = loglines.concat(ll); + } + } + } else if (container == 'builder' || container == 'fetcher') { + const ll = await this.fetchLogs(namespace, pod.metadata.name, "kuberoapp-"+container, pipelineName, phaseName, appName) + loglines = loglines.concat(ll); + } else { + // leace the loglines empty + console.log('unknown container: '+container); + } + } + } + } + return loglines; + } + + public async fetchLogs(namespace: string, podName: string, containerName: string, pipelineName: string, phaseName: string, appName: string): Promise { + let loglines: ILoglines[] = []; + + const logStream = new Stream.PassThrough(); + let logs: String = ''; + logStream.on('data', (chunk: any) => { + //console.log(chunk.toString()); + logs += chunk.toString(); + }); + + try { + await this.kubectl.log.log(namespace, podName, containerName, logStream, {follow: false, tailLines: 80, pretty: false, timestamps: true}) + } catch (error) { + console.log("error getting logs for "+podName+" "+containerName); + return []; + } + + // sleep for 1 second to wait for all logs to be collected + await new Promise(r => setTimeout(r, 300)); + + // split loglines into array + const loglinesArray = logs.split('\n').reverse(); + for (const logline of loglinesArray) { + if (logline.length > 0) { + // split after first whitespace + const loglineArray = logline.split(/(?<=^\S+)\s/); + const loglineDate = new Date(loglineArray[0]); + const loglineText = loglineArray[1]; + + loglines.push({ + id: uuidv4(), + time: loglineDate.getTime(), + pipeline: pipelineName, + phase: phaseName, + app: appName, + pod: podName, + podID: podName.split('-')[3]+'-'+podName.split('-')[4], + container: containerName, + color: this.logcolor(podName), + log: loglineText + }); + } + } + + return loglines; + } + + + +} diff --git a/server-refactored-v3/src/metrics/metrics.controller.ts b/server-refactored-v3/src/metrics/metrics.controller.ts index 8d25b8ec..c80dbe8a 100644 --- a/server-refactored-v3/src/metrics/metrics.controller.ts +++ b/server-refactored-v3/src/metrics/metrics.controller.ts @@ -9,7 +9,7 @@ export class MetricsController { ) {} @ApiOperation({ summary: 'Get metrics for a specific app' }) - @Get('/:pipeline/:phase/:app') + @Get('/resources/:pipeline/:phase/:app') async getMetrics( @Param('pipeline') pipeline: string, @Param('phase') phase: string, @@ -17,4 +17,13 @@ export class MetricsController { ) { return this.metricsService.getPodMetrics(pipeline, phase, app); } + + @ApiOperation({ summary: 'Get uptimes for pods on a Namespace' }) + @Get('/uptimes/:pipeline/:phase') + async getUptimes( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + ) { + return this.metricsService.getUptimes(pipeline, phase); + } } diff --git a/server-refactored-v3/src/metrics/metrics.service.ts b/server-refactored-v3/src/metrics/metrics.service.ts index dec99c21..032f8799 100644 --- a/server-refactored-v3/src/metrics/metrics.service.ts +++ b/server-refactored-v3/src/metrics/metrics.service.ts @@ -12,7 +12,7 @@ export class MetricsService { //options: MetricsOptions private kubectl: KubernetesService ) { - //TODO: Load options from settings + //TODO: Migration -> Load options from settings or config const options = { enabled: true, endpoint: 'http://prometheus.localhost' @@ -358,4 +358,9 @@ export class MetricsService { const namespace = pipelineName+'-'+phaseName; return this.kubectl.getPodMetrics(namespace, appName); } + + public getUptimes(pipelineName: string, phaseName: string) { + const namespace = pipelineName+'-'+phaseName; + return this.kubectl.getPodUptimes(namespace); + } } \ No newline at end of file diff --git a/server-refactored-v3/src/security/security.service.ts b/server-refactored-v3/src/security/security.service.ts index e1669b1a..a6fc1730 100644 --- a/server-refactored-v3/src/security/security.service.ts +++ b/server-refactored-v3/src/security/security.service.ts @@ -39,7 +39,7 @@ export class SecurityService { const appresult = await this.appsService.getApp(pipeline, phase, appName) - const app = appresult?.body as IKubectlApp; + const app = appresult as IKubectlApp; const logPod = await this.kubectl.getLatestPodByLabel(namespace, `vulnerabilityscan=${appName}`); diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts index eb51a3ad..a8f3f191 100644 --- a/server-refactored-v3/src/settings/settings.controller.ts +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -20,12 +20,6 @@ export class SettingsController { return this.settingsService.getBanner(); } - @ApiOperation({ summary: 'Get a list of allredy taken domains on this Kubernets cluster' }) - @Get('/domains') - async getDomains() { - return this.settingsService.getDomains(); - } - @ApiOperation({ summary: 'Get the templates settings' }) @Get('/templates') async getTemplates() { @@ -50,14 +44,23 @@ export class SettingsController { async getRunpacks() { return this.settingsService.getRunpacks(); } -/* + + @ApiOperation({ summary: 'Get the configured cluster issuer' }) @Get('/clusterissuer') async getClusterIssuer() { return this.settingsService.getClusterIssuer(); } + + @ApiOperation({ summary: 'List buildpacks' }) @Get('/buildpacks') async getBuildpacks() { return this.settingsService.getBuildpacks(); } -*/ + + @ApiOperation({ summary: 'List available pod sizes' }) + @Get('/podsizes') + async getPodSizes() { + return this.settingsService.getPodSizes(); + } + } diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 0a49bc54..732de771 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -6,6 +6,8 @@ import { readFileSync, writeFileSync } from 'fs'; import YAML from 'yaml' import { join } from 'path'; import { Context } from '@kubernetes/client-node'; +import { Buildpack } from './buildpack/buildpack'; +import { PodSize } from './podsize/podsize'; @Injectable() export class SettingsService { @@ -84,7 +86,6 @@ export class SettingsService { const kuberoCRD = await this.readConfigFromKubernetes() return kuberoCRD.kubero.config } else { - console.log("aaaa", this.readConfigFromFS()) return this.readConfigFromFS() } } @@ -148,17 +149,6 @@ export class SettingsService { return registry } - public async getDomains(): Promise { - let allIngress = await this.kubectl.getAllIngress() - let domains: string[] = [] - allIngress.forEach((ingress: any) => { - ingress.spec.rules.forEach((rule: any) => { - domains.push(rule.host) - }) - }) - return domains - } - public async getBanner(): Promise { let defaultbanner = { show: false, @@ -314,11 +304,44 @@ export class SettingsService { public getRunpacks(): any[] { return this.runningConfig.buildpacks || [] } -/* - public async getClusterIssuer(): Promise { + + public async getClusterIssuer(): Promise<{clusterissuer: string}> { const namespace = process.env.KUBERO_NAMESPACE || "kubero" - const kuberoes = await this.kubectl.getClusterIssuer(namespace) - return kuberoes.kubero.config.clusterissuer + let kuberoes = await this.kubectl.getKuberoConfig(namespace) + if (kuberoes == undefined) { + return { clusterissuer: "not-configured" } + } + return { + clusterissuer: kuberoes.spec.kubero.config.clusterissuer || "not-configured" + } } -*/ + + public async getBuildpacks() { + let buildpackList: Buildpack[] = []; + + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + let kuberoes = await this.kubectl.getKuberoConfig(namespace) + + for (const buildpack of kuberoes.spec.kubero.config.buildpacks) { + const b = new Buildpack(buildpack); + buildpackList.push(b); + } + + return buildpackList; + } + + public async getPodSizes() { + let podSizeList: PodSize[] = []; + + const namespace = process.env.KUBERO_NAMESPACE || "kubero" + let kuberoes = await this.kubectl.getKuberoConfig(namespace) + + for (const podSize of kuberoes.spec.kubero.config.podSizeList) { + const p = new PodSize(podSize); + podSizeList.push(p); + } + + return podSizeList; + } + } diff --git a/server-refactored-v3/src/templates/templates.controller.ts b/server-refactored-v3/src/templates/templates.controller.ts deleted file mode 100644 index 7dcaca63..00000000 --- a/server-refactored-v3/src/templates/templates.controller.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; - -@Controller('templates') -export class TemplatesController { - constructor() {} - - @Get('/catalogs') - async getTemplates() { - return 'getTemplates'; - } -} diff --git a/server-refactored-v3/src/templates/templates.module.ts b/server-refactored-v3/src/templates/templates.module.ts deleted file mode 100644 index 0cdf3ee9..00000000 --- a/server-refactored-v3/src/templates/templates.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TemplatesController } from './templates.controller'; - -@Module({ - controllers: [TemplatesController] -}) -export class TemplatesModule {} diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 9cb14ffc..6aec0b9e 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -1037,6 +1037,7 @@ export class Kubero { } } + //Migrated to logs private logcolor(str: string) { let hash = 0; for (var i = 0; i < str.length; i++) { @@ -1050,6 +1051,7 @@ export class Kubero { return color; } + //Migrated to logs public emitLogs(pipelineName: string, phaseName: string, appName: string, podName: string, container: string) { const logStream = new Stream.PassThrough(); @@ -1094,6 +1096,7 @@ export class Kubero { } } + //Migrated to logs public startLogging(pipelineName: string, phaseName: string, appName: string) { const contextName = this.getContext(pipelineName, phaseName); const namespace = pipelineName+'-'+phaseName; @@ -1117,6 +1120,7 @@ export class Kubero { } } + //Migrated to logs public async getLogsHistory(pipelineName: string, phaseName: string, appName: string, container: string) { const contextName = this.getContext(pipelineName, phaseName); const namespace = pipelineName+'-'+phaseName; @@ -1148,6 +1152,7 @@ export class Kubero { return loglines; } + //Migrated to logs public async fetchLogs(namespace: string, podName: string, containerName: string, pipelineName: string, phaseName: string, appName: string): Promise { let loglines: ILoglines[] = []; @@ -1234,6 +1239,7 @@ export class Kubero { return repositories; } + //Migration to settings public getBuildpacks() { let buildpackList: Buildpack[] = []; for (const buildpack of this.config.buildpacks) { @@ -1244,10 +1250,12 @@ export class Kubero { return buildpackList; } + //Migration to kubernetes public getEvents(namespace: string) { return this.kubectl.getEvents(namespace); } + //Migration to metrics public getPodUptime(pipelineName: string, phaseName: string) { const namespace = pipelineName+'-'+phaseName; return this.kubectl.getPodUptimes(namespace); @@ -1266,6 +1274,7 @@ export class Kubero { return this.kubectl.getIngressClasses(); } + //Migrated to kubernetes public getStorageglasses() { return this.kubectl.getStorageglasses(); } From 4b72c97de72389e735abf777f8370627138231b5 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Mon, 10 Feb 2025 23:32:13 +0100 Subject: [PATCH 27/65] add some dto for kubernetes controller --- .../src/kubernetes/kubernetes.controller.ts | 18 +++++++++++++++--- .../src/kubernetes/kubernetes.dto.ts | 17 +++++++++++++++++ .../src/kubernetes/kubernetes.interface.ts | 8 ++++++++ .../src/kubernetes/kubernetes.service.ts | 8 ++++---- 4 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 server-refactored-v3/src/kubernetes/kubernetes.dto.ts diff --git a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts index 91e14b70..fc64f89b 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts @@ -1,6 +1,7 @@ import { Controller, Get, Query } from '@nestjs/common'; import { KubernetesService } from './kubernetes.service'; -import { ApiOperation } from '@nestjs/swagger'; +import { ApiOperation, ApiOkResponse, ApiResponse } from '@nestjs/swagger'; +import { StorageClassDTO } from './kubernetes.dto'; @Controller({ path: 'api/kubernetes', version: '1' }) export class KubernetesController { @@ -8,21 +9,32 @@ export class KubernetesController { private readonly kubernetesService: KubernetesService ) {} + @ApiResponse({ status: 200, description: 'List of available contexts' }) @ApiOperation({ summary: 'Get the Kubernetes events in a specific namespace' }) @Get('events') async getEvents(@Query('namespace') namespace: string) { return this.kubernetesService.getEvents(namespace); } + @ApiOkResponse({ + description: 'A List of available contexts', + type: StorageClassDTO, + isArray: true + }) @ApiOperation({ summary: 'Get the available storage classes' }) @Get('storageclasses') - async getStorageClasses() { + async getStorageClasses(): Promise { return this.kubernetesService.getStorageClasses(); } + @ApiOkResponse({ + description: 'Already taken domains', + type: [String], + isArray: true + }) @ApiOperation({ summary: 'Get a list of allredy taken domains on this Kubernets cluster' }) @Get('domains') - async getDomains() { + async getDomains(): Promise { return this.kubernetesService.getDomains(); } diff --git a/server-refactored-v3/src/kubernetes/kubernetes.dto.ts b/server-refactored-v3/src/kubernetes/kubernetes.dto.ts new file mode 100644 index 00000000..18c14a13 --- /dev/null +++ b/server-refactored-v3/src/kubernetes/kubernetes.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class StorageClassDTO { + @ApiProperty() + name: string; + + @ApiProperty() + provisioner: string; + + @ApiProperty() + reclaimPolicy: string; + + @ApiProperty() + volumeBindingMode: string; + //allowVolumeExpansion: boolean; + //mountOptions: string[]; +} \ No newline at end of file diff --git a/server-refactored-v3/src/kubernetes/kubernetes.interface.ts b/server-refactored-v3/src/kubernetes/kubernetes.interface.ts index 6a39ef89..493f0699 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.interface.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.interface.ts @@ -48,3 +48,11 @@ export interface IKubectlApp spec: IApp ; } +export interface IStorageClass { + name: string; + provisioner: string; + reclaimPolicy: string; + volumeBindingMode: string; + //allowVolumeExpansion: boolean; + //mountOptions: string[]; +} \ No newline at end of file diff --git a/server-refactored-v3/src/kubernetes/kubernetes.service.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts index a3a16200..de0567f6 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.service.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -1,5 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList, IKubectlApp} from './kubernetes.interface'; +import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList, IKubectlApp, IStorageClass} from './kubernetes.interface'; import { IPipeline, } from '../pipelines/pipelines.interface'; import { KubectlPipeline } from '../pipelines/pipeline/pipeline'; import { KubectlApp, App } from '../apps/app/app'; @@ -721,8 +721,8 @@ export class KubernetesService { } - public async getStorageClasses(): Promise { - let ret: { name: string | undefined; provisioner: string; reclaimPolicy: string | undefined; volumeBindingMode: string | undefined; }[] = []; + public async getStorageClasses(): Promise { + let ret: IStorageClass[] = []; try { const storageClasses = await this.storageV1Api.listStorageClass(); for (let i = 0; i < storageClasses.body.items.length; i++) { @@ -734,7 +734,7 @@ export class KubernetesService { volumeBindingMode: sc.volumeBindingMode, //allowVolumeExpansion: sc.allowVolumeExpansion, //parameters: sc.parameters - } + } as IStorageClass; ret.push(storageClass); } } catch (error) { From e17c5b83046648572b61f73a02578c4bd486dfe1 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Tue, 11 Feb 2025 02:54:25 +0100 Subject: [PATCH 28/65] add helmet and cors --- client/package.json | 4 ++-- server-refactored-v3/package.json | 1 + server-refactored-v3/src/main.ts | 15 +++++++++++++-- server-refactored-v3/yarn.lock | 5 +++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/client/package.json b/client/package.json index 5ef8a6b4..ec71afe8 100644 --- a/client/package.json +++ b/client/package.json @@ -3,8 +3,8 @@ "private": true, "license": "GPL-3.0", "scripts": { - "dev": "vite", - "watch": "vue-tsc --noEmit && vite build --watch", + "run": "vite", + "dev": "vue-tsc --noEmit && vite build --watch", "build": "vue-tsc --noEmit && vite build", "preview": "vite preview", "lint": "eslint . --fix --ignore-path .gitignore" diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index c47f7b3d..bbaded2e 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -41,6 +41,7 @@ "dotenv": "^16.4.7", "git-url-parse": "^16.0.0", "gitea-js": "^1.23.0", + "helmet": "^8.0.0", "passport": "^0.7.0", "passport-github2": "^0.1.12", "passport-local": "^1.0.0", diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 40c0d430..636b4a7d 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -1,19 +1,30 @@ import { NestFactory } from '@nestjs/core'; -import { Logger } from '@nestjs/common'; +import { Logger, } from '@nestjs/common'; import { CustomConsoleLogger } from './logger/logger'; +import { LogLevel } from '@nestjs/common/services/logger.service'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; + +import helmet from 'helmet'; + import * as dotenv from 'dotenv'; dotenv.config(); async function bootstrap() { + + const logLevels = process.env.LOGLEVELS?.split(',') ?? ['log', 'fatal', 'error', 'warn', 'debug', 'verbose']; + Logger.log(`Log levels: ${logLevels}`, 'Bootstrap'); + const app = await NestFactory.create(AppModule, { logger: new CustomConsoleLogger({ prefix: 'Kubero', - //logLevels: ['log', 'error', 'warn', 'debug', 'verbose'], + logLevels: logLevels as LogLevel[], }), + cors: true, }); + app.use(helmet()); + const config = new DocumentBuilder() .setTitle('Kubero') .setDescription('Kubero is a web-based tool deploy applications on a Kubernetes clusters. It provides a simple and intuitive interface to manage your clusters, applications, and pipelines.') diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 552d294d..38c3c012 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -3988,6 +3988,11 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +helmet@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-8.0.0.tgz#05370fb1953aa7b81bd0ddfa459221247be6ea5c" + integrity sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw== + hexoid@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-2.0.0.tgz#fb36c740ebbf364403fa1ec0c7efd268460ec5b9" From c199183e4c6697914fdf9e223d0d03165fce9e55 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Tue, 11 Feb 2025 22:48:52 +0100 Subject: [PATCH 29/65] migration, add pipeline endpoints --- client/src/components/pipelines/list.vue | 2 +- server-refactored-v3/src/apps/apps.module.ts | 6 +- .../kubernetes/{ => dto}/kubernetes.dto.ts | 0 .../src/kubernetes/kubernetes.controller.ts | 2 +- server-refactored-v3/src/main.ts | 7 +- .../src/pipelines/dto/replacePipeline.dto.ts | 40 +++++++ .../src/pipelines/pipelines.controller.ts | 68 +++++++++-- .../src/pipelines/pipelines.module.ts | 4 +- .../src/pipelines/pipelines.service.ts | 107 ++++++++++++------ server/src/kubero.ts | 2 + 10 files changed, 189 insertions(+), 49 deletions(-) rename server-refactored-v3/src/kubernetes/{ => dto}/kubernetes.dto.ts (100%) create mode 100644 server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts diff --git a/client/src/components/pipelines/list.vue b/client/src/components/pipelines/list.vue index 59572957..76f82cff 100644 --- a/client/src/components/pipelines/list.vue +++ b/client/src/components/pipelines/list.vue @@ -146,7 +146,7 @@ type Pipeline = { const socket = useKuberoStore().kubero.socket as any; -socket.on('updatedPipelines', (instances: any) => { +socket.on('updatePipeline', (instances: any) => { //console.log("updatedPipelines", instances); loadPipelinesList(); }); diff --git a/server-refactored-v3/src/apps/apps.module.ts b/server-refactored-v3/src/apps/apps.module.ts index 06a7cb42..db28869b 100644 --- a/server-refactored-v3/src/apps/apps.module.ts +++ b/server-refactored-v3/src/apps/apps.module.ts @@ -1,11 +1,13 @@ import { Module } from '@nestjs/common'; import { AppsService } from './apps.service'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; -import { PipelinesService } from '../pipelines/pipelines.service'; import { AppsController } from './apps.controller'; +import { PipelinesService } from '../pipelines/pipelines.service'; +import { NotificationsService } from '../notifications/notifications.service'; +import { EventsGateway } from 'src/events/events.gateway'; @Module({ - providers: [AppsService, KubernetesModule, PipelinesService], + providers: [AppsService, KubernetesModule, PipelinesService, NotificationsService, EventsGateway], exports: [AppsService], controllers: [AppsController], }) diff --git a/server-refactored-v3/src/kubernetes/kubernetes.dto.ts b/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts similarity index 100% rename from server-refactored-v3/src/kubernetes/kubernetes.dto.ts rename to server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts diff --git a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts index fc64f89b..ea3b6f2d 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Query } from '@nestjs/common'; import { KubernetesService } from './kubernetes.service'; import { ApiOperation, ApiOkResponse, ApiResponse } from '@nestjs/swagger'; -import { StorageClassDTO } from './kubernetes.dto'; +import { StorageClassDTO } from './dto/kubernetes.dto'; @Controller({ path: 'api/kubernetes', version: '1' }) export class KubernetesController { diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 636b4a7d..a1363d8d 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -23,7 +23,12 @@ async function bootstrap() { cors: true, }); - app.use(helmet()); + app.use(helmet({ + contentSecurityPolicy: false, + strictTransportSecurity: false, + crossOriginOpenerPolicy: false, + crossOriginEmbedderPolicy: false, + })); const config = new DocumentBuilder() .setTitle('Kubero') diff --git a/server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts b/server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts new file mode 100644 index 00000000..d9293a12 --- /dev/null +++ b/server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts @@ -0,0 +1,40 @@ + +import { ApiProperty } from '@nestjs/swagger'; +import { IBuildpack, IRegistry } from '../../settings/settings.interface'; +import { IPipelinePhase, IgitLink } from '../pipelines.interface'; + +export class CreatePipelineDTO { + + @ApiProperty() + pipelineName: string; + + @ApiProperty() + domain: string; + + @ApiProperty() + reviewapps: boolean; + + @ApiProperty() + phases: IPipelinePhase[]; + + @ApiProperty() + buildpack: IBuildpack + + @ApiProperty() + git: IgitLink; + + @ApiProperty() + registry: IRegistry; + + @ApiProperty() + dockerimage: string; + + @ApiProperty() + deploymentstrategy: 'git' | 'docker'; + + @ApiProperty() + buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; + + @ApiProperty() + resourceVersion?: string; // required to update resource, not part of spec +} \ No newline at end of file diff --git a/server-refactored-v3/src/pipelines/pipelines.controller.ts b/server-refactored-v3/src/pipelines/pipelines.controller.ts index 32dc5445..20bec781 100644 --- a/server-refactored-v3/src/pipelines/pipelines.controller.ts +++ b/server-refactored-v3/src/pipelines/pipelines.controller.ts @@ -1,6 +1,9 @@ -import { Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import { PipelinesService } from './pipelines.service'; import { ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { CreatePipelineDTO } from './dto/replacePipeline.dto'; +import { IUser } from '../auth/auth.interface'; +import { IPipeline } from './pipelines.interface'; @Controller({ path: 'api/pipelines', version: '1' }) export class PipelinesController { @@ -14,9 +17,29 @@ export class PipelinesController { } @ApiOperation({ summary: 'Create a new pipeline' }) - @Post('/:pipeline') - async createPipeline() { - return 'Pipeline updated'; + @Post('/') + async createPipeline(@Body() pl: CreatePipelineDTO) { + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890' + }; + + const pipeline: IPipeline = { + name: pl.pipelineName, + domain: pl.domain, + phases: pl.phases, + buildpack: pl.buildpack, + reviewapps: pl.reviewapps, + dockerimage: pl.dockerimage, + git: pl.git, + registry: pl.registry as any, + deploymentstrategy: pl.deploymentstrategy, + buildstrategy: pl.buildstrategy, + }; + return this.pipelinesService.createPipeline(pipeline, user); } @ApiOperation({ summary: 'Get a soecific pipeline' }) @@ -29,14 +52,43 @@ export class PipelinesController { @ApiOperation({ summary: 'Update a pipeline' }) @Put('/:pipeline') - async updatePipeline() { - return 'Pipeline updated'; + async updatePipeline(@Body() pl: CreatePipelineDTO) { + + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890' + }; + + const pipeline: IPipeline = { + name: pl.pipelineName, + domain: pl.domain, + phases: pl.phases, + buildpack: pl.buildpack, + reviewapps: pl.reviewapps, + dockerimage: pl.dockerimage, + git: pl.git, + registry: pl.registry as any, + deploymentstrategy: pl.deploymentstrategy, + buildstrategy: pl.buildstrategy, + }; + return this.pipelinesService.updatePipeline(pipeline, pl.resourceVersion as string, user); } @ApiOperation({ summary: 'Delete a pipeline' }) @Delete('/:pipeline') - async deletePipeline() { - return 'Pipeline deleted'; + async deletePipeline( + @Param('pipeline') pipeline: string, + ) { + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890' + }; + return this.pipelinesService.deletePipeline(pipeline, user); } @ApiOperation({ summary: 'Get all apps for a pipeline' }) diff --git a/server-refactored-v3/src/pipelines/pipelines.module.ts b/server-refactored-v3/src/pipelines/pipelines.module.ts index 263de029..93f5d7e2 100644 --- a/server-refactored-v3/src/pipelines/pipelines.module.ts +++ b/server-refactored-v3/src/pipelines/pipelines.module.ts @@ -1,11 +1,13 @@ import { Global, Module } from '@nestjs/common'; import { PipelinesController } from './pipelines.controller'; import { PipelinesService } from './pipelines.service'; +import { NotificationsService } from '../notifications/notifications.service'; +import { EventsGateway } from 'src/events/events.gateway'; @Global() @Module({ controllers: [PipelinesController], - providers: [PipelinesService], + providers: [PipelinesService, NotificationsService, EventsGateway], exports: [PipelinesService], }) export class PipelinesModule {} diff --git a/server-refactored-v3/src/pipelines/pipelines.service.ts b/server-refactored-v3/src/pipelines/pipelines.service.ts index 1d21ae63..1ac6a442 100644 --- a/server-refactored-v3/src/pipelines/pipelines.service.ts +++ b/server-refactored-v3/src/pipelines/pipelines.service.ts @@ -2,14 +2,20 @@ import { Injectable, Logger } from '@nestjs/common'; import { IPipelineList, IPipeline } from './pipelines.interface'; import { KubernetesService } from 'src/kubernetes/kubernetes.service'; import { Buildpack } from '../settings/buildpack/buildpack'; -import { IUser } from 'src/auth/auth.interface'; +import { IUser } from '../auth/auth.interface'; +import { NotificationsService } from '../notifications/notifications.service'; +import { INotification } from '../notifications/notifications.interface'; +import { CreatePipelineDTO } from './dto/replacePipeline.dto'; @Injectable() export class PipelinesService { private readonly logger = new Logger(PipelinesService.name); //private pipelineStateList = [] as IPipeline[]; //DEPRECATED: should not be used but reloaded live state - constructor(private kubectl: KubernetesService) {} + constructor( + private kubectl: KubernetesService, + private notificationsService: NotificationsService, +) {} public async listPipelines(): Promise { let pipelines = await this.kubectl.getPipelinesList(); @@ -116,12 +122,11 @@ export class PipelinesService { if (pipeline) { await this.kubectl.deletePipeline(pipelineName); - await new Promise(resolve => setTimeout(resolve, 5000)); // needs some extra time to delete the namespace + await new Promise(resolve => setTimeout(resolve, 1000)); // needs some extra time to delete the namespace //this.updateState(); - /* Might be moved to a notification middleware const m = { - 'name': 'deletePipeline', + 'name': 'updatePipeline', 'user': user.username, 'resource': 'pipeline', 'action': 'delete', @@ -134,46 +139,78 @@ export class PipelinesService { 'pipeline': pipeline } } as INotification; - this.notification.send(m, this._io); - */ + this.notificationsService.send(m); } }) .catch(error => { this.logger.error(error); }); } + public async updatePipeline(pipeline: IPipeline, resourceVersion: string, user: IUser) { + this.logger.debug('update Pipeline: '+pipeline.name); - /* - public updateState() { - this.pipelineStateList = []; - this.appStateList = []; - this.listPipelines().then(pl => { - for (const pipeline of pl.items as IPipeline[]) { - this.pipelineStateList.push(pipeline); + if ( process.env.KUBERO_READONLY == 'true'){ + this.logger.log('KUBERO_READONLY is set to true, not updating pipelline ' + pipeline.name); + return; + } - for (const phase of pipeline.phases) { + const currentPL = await this.kubectl.getPipeline(pipeline.name) + .catch(error => { + this.logger.error(error); + }); - if (phase.enabled == true) { - debug.log("🔁 Loading Namespace: "+pipeline.name+"-"+phase.name); - this.listAppsInNamespace(pipeline.name, phase.name) - .then(appsList => { - if (appsList) { - for (const app of appsList.items) { - debug.log("🔁 Loading App: "+app.spec.name); - this.appStateList.push(app.spec); - } - } - }) - .catch(error => { - debug.log(error); - }) - } - } + pipeline.git.keys.priv = currentPL?.spec.git.keys.priv; + pipeline.git.keys.pub = currentPL?.spec.git.keys.pub; + + // Create the Pipeline CRD + await this.kubectl.updatePipeline(pipeline, resourceVersion); + //this.updateState(); + + await new Promise(resolve => setTimeout(resolve, 500)) + const m = { + 'name': 'updatePipeline', + 'user': user.username, + 'resource': 'pipeline', + 'action': 'update', + 'severity': 'normal', + 'message': 'Updated pipeline: '+pipeline.name, + 'pipelineName':pipeline.name, + 'phaseName': '', + 'appName': '', + 'data': { + 'pipeline': pipeline } + } as INotification; + this.notificationsService.send(m); + } + + public async createPipeline(pipeline: IPipeline, user: IUser) { + this.logger.debug('create Pipeline: '+pipeline.name); + + if ( process.env.KUBERO_READONLY == 'true'){ + console.log('KUBERO_READONLY is set to true, not creting pipeline '+ pipeline.name); + return; } - ).catch(error => { - debug.log(error); - }); + + // Create the Pipeline CRD + await this.kubectl.createPipeline(pipeline); + //this.updateState(); + + const m = { + 'name': 'updatePipeline', + 'user': user.username, + 'resource': 'pipeline', + 'action': 'created', + 'severity': 'normal', + 'message': 'Created new pipeline: '+pipeline.name, + 'pipelineName':pipeline.name, + 'phaseName': '', + 'appName': '', + 'data': { + 'pipeline': pipeline + } + } as INotification; + this.notificationsService.send(m); } - */ + } diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 6aec0b9e..09dd8183 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -189,6 +189,7 @@ export class Kubero { } } + //Migrated to Pipelines // creates a new pipeline in the same namespace as the kubero app public async newPipeline(pipeline: IPipeline, user: User) { debug.debug('create Pipeline: '+pipeline.name); @@ -219,6 +220,7 @@ export class Kubero { this.notification.send(m, this._io); } + //Migrated to pipelines // updates a new pipeline in the same namespace as the kubero app public async updatePipeline(pipeline: IPipeline, resourceVersion: string, user: User) { debug.debug('update Pipeline: '+pipeline.name); From c092b68aa90c9d436d181e7e2ebcd5f154724122 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Tue, 11 Feb 2025 23:41:18 +0100 Subject: [PATCH 30/65] migrate repo connection --- server-refactored-v3/package.json | 3 + server-refactored-v3/src/main.ts | 20 + .../src/repo/git/bitbucket.ts | 18 +- server-refactored-v3/src/repo/git/gitea.ts | 14 +- server-refactored-v3/src/repo/git/github.ts | 26 +- server-refactored-v3/src/repo/git/gitlab.ts | 10 +- server-refactored-v3/src/repo/git/gogs.ts | 16 +- server-refactored-v3/src/repo/git/repo.ts | 23 +- .../src/repo/repo.controller.ts | 37 +- server-refactored-v3/yarn.lock | 520 +++++++++++++++++- 10 files changed, 615 insertions(+), 72 deletions(-) diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index bbaded2e..07044f07 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -49,6 +49,8 @@ "prometheus-query": "^3.4.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "sqlite3": "^5.1.7", + "sshpk": "^1.18.0", "yaml": "^2.7.0" }, "devDependencies": { @@ -65,6 +67,7 @@ "@types/passport-github2": "^1.2.9", "@types/passport-local": "^1.0.38", "@types/passport-oauth2": "^1.4.17", + "@types/sshpk": "^1.17.4", "@types/supertest": "^6.0.2", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index a1363d8d..1f25e363 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -28,6 +28,26 @@ async function bootstrap() { strictTransportSecurity: false, crossOriginOpenerPolicy: false, crossOriginEmbedderPolicy: false, + /* suggested settings. Requires further testing. + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "example.com"], + styleSrc: ["'self'", "example.com"], + imgSrc: ["'self'", "data:"], + connectSrc: ["'self'"], + fontSrc: ["'self'", "https:", "data:"], + objectSrc: ["'none'"], + frameAncestors: ["'self'"], + formAction: ["'self'"], + upgradeInsecureRequests: [], + }, + }, + frameguard: { action: 'deny' }, + strictTransportSecurity: { maxAge: 63072000, includeSubDomains: true }, + crossOriginOpenerPolicy: { policy: 'same-origin' }, + crossOriginEmbedderPolicy: { policy: 'require-corp' }, + */ })); const config = new DocumentBuilder() diff --git a/server-refactored-v3/src/repo/git/bitbucket.ts b/server-refactored-v3/src/repo/git/bitbucket.ts index d47b7b0f..ad9cc510 100644 --- a/server-refactored-v3/src/repo/git/bitbucket.ts +++ b/server-refactored-v3/src/repo/git/bitbucket.ts @@ -79,7 +79,7 @@ export class BitbucketApi extends Repo { } catch (e) { let res = e as RequestError; - debug.log("Repository not found: "+ gitrepo); + this.logger.log("Repository not found: "+ gitrepo); ret = { status: res.status, statusText: 'not found', @@ -218,7 +218,7 @@ export class BitbucketApi extends Repo { } } catch (e) { let res = e as RequestError; - debug.log("Error adding deploy key: "+ res); + this.logger.log("Error adding deploy key: "+ res); } return ret @@ -233,7 +233,7 @@ export class BitbucketApi extends Repo { } else if (event === 'pullrequest:created') { github_event = 'pull_request'; } else { - debug.log('ERROR: untranslated Bitbucket event: '+event); + this.logger.log('ERROR: untranslated Bitbucket event: '+event); return false; } @@ -269,7 +269,7 @@ export class BitbucketApi extends Repo { return webhook; } catch (error) { - debug.log(error) + this.logger.log(error) return false; } } @@ -289,7 +289,7 @@ export class BitbucketApi extends Repo { } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; } @@ -309,7 +309,7 @@ export class BitbucketApi extends Repo { return branches.data.values.map((branch: any) => branch.name); } } catch (error) { - debug.log(error) + this.logger.log(error) } return []; @@ -334,7 +334,7 @@ export class BitbucketApi extends Repo { ret = branches.data.values.map((branch: any) => branch.name); } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -347,7 +347,7 @@ export class BitbucketApi extends Repo { ret = ret.concat(tags.data.values.map((tag: any) => tag.name)); } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -360,7 +360,7 @@ export class BitbucketApi extends Repo { ret = ret.concat(commits.data.values.map((commit: any) => commit.hash)); } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; diff --git a/server-refactored-v3/src/repo/git/gitea.ts b/server-refactored-v3/src/repo/git/gitea.ts index 9c3e6a23..1ab743c9 100644 --- a/server-refactored-v3/src/repo/git/gitea.ts +++ b/server-refactored-v3/src/repo/git/gitea.ts @@ -200,9 +200,9 @@ export class GiteaApi extends Repo { debug.debug('Gitea webhook signature is valid for event: '+delivery); verified = true; } else { - debug.log('ERROR: invalid signature for event: '+delivery); - debug.log('Hash: '+hash); - debug.log('Signature: '+signature); + this.logger.log('ERROR: invalid signature for event: '+delivery); + this.logger.log('Hash: '+hash); + this.logger.log('Signature: '+signature); verified = false; return false; } @@ -286,7 +286,7 @@ export class GiteaApi extends Repo { ret.push(branch.name) } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -295,7 +295,7 @@ export class GiteaApi extends Repo { ret.push(tag.name) } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -304,7 +304,7 @@ export class GiteaApi extends Repo { ret.push(commit.sha) } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; @@ -344,7 +344,7 @@ export class GiteaApi extends Repo { } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; diff --git a/server-refactored-v3/src/repo/git/github.ts b/server-refactored-v3/src/repo/git/github.ts index e9e68ec1..8e8af676 100644 --- a/server-refactored-v3/src/repo/git/github.ts +++ b/server-refactored-v3/src/repo/git/github.ts @@ -64,7 +64,7 @@ export class GithubApi extends Repo { } } catch (e) { let res = e as RequestError; - debug.log("Repository not found: "+ gitrepo); + this.logger.log("Repository not found: "+ gitrepo); ret = { status: res.status, statusText: 'not found', @@ -147,7 +147,7 @@ export class GithubApi extends Repo { }) for (let webhook of existingWebhooksRes.data) { if (webhook.config.url === url) { - debug.log("Webhook already exists"); + this.logger.log("Webhook already exists"); ret = { status: res.status, @@ -213,7 +213,7 @@ export class GithubApi extends Repo { } } catch (e) { let res = e as RequestError; - debug.log("Error adding deploy key: "+ res); + this.logger.log("Error adding deploy key: "+ res); } return ret @@ -229,9 +229,9 @@ export class GithubApi extends Repo { debug.debug('Github webhook signature is valid for event: '+delivery); verified = true; } else { - debug.log('ERROR: invalid signature for event: '+delivery); - debug.log('Hash: '+hash); - debug.log('Signature: '+signature); + this.logger.log('ERROR: invalid signature for event: '+delivery); + this.logger.log('Hash: '+hash); + this.logger.log('Signature: '+signature); verified = false; return false; } @@ -268,7 +268,7 @@ export class GithubApi extends Repo { return webhook; } catch (error) { - debug.log(error) + this.logger.log(error) return false; } } @@ -285,7 +285,7 @@ export class GithubApi extends Repo { ret.push(repo.ssh_url) } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; } @@ -305,7 +305,7 @@ export class GithubApi extends Repo { ret.push(branch.name) } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; @@ -327,7 +327,7 @@ export class GithubApi extends Repo { ret.push(branch.name) } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -339,7 +339,7 @@ export class GithubApi extends Repo { ret.push(tag.name) } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -351,7 +351,7 @@ export class GithubApi extends Repo { ret.push(commit.sha) } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; @@ -393,7 +393,7 @@ export class GithubApi extends Repo { ret.push(p) } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; diff --git a/server-refactored-v3/src/repo/git/gitlab.ts b/server-refactored-v3/src/repo/git/gitlab.ts index 599b4fba..95cf19d5 100644 --- a/server-refactored-v3/src/repo/git/gitlab.ts +++ b/server-refactored-v3/src/repo/git/gitlab.ts @@ -223,9 +223,9 @@ export class GitlabApi extends Repo { debug.debug('Gitlab webhook signature is valid for event: '+delivery); verified = true; } else { - debug.log('ERROR: invalid token/secret for event: '+delivery); - debug.log('Secret: '+secret); - debug.log('Token : '+token); + this.logger.log('ERROR: invalid token/secret for event: '+delivery); + this.logger.log('Secret: '+secret); + this.logger.log('Token : '+token); verified = false; return false; } @@ -237,7 +237,7 @@ export class GitlabApi extends Repo { } else if (event === 'Merge Request Hook') { github_event = 'pull_request'; } else { - debug.log('ERROR: unknown event: '+event); + this.logger.log('ERROR: unknown event: '+event); return false; } @@ -274,7 +274,7 @@ export class GitlabApi extends Repo { return webhook; } catch (error) { - debug.log(error) + this.logger.log(error) return false; } } diff --git a/server-refactored-v3/src/repo/git/gogs.ts b/server-refactored-v3/src/repo/git/gogs.ts index 433d4c4f..de726d25 100644 --- a/server-refactored-v3/src/repo/git/gogs.ts +++ b/server-refactored-v3/src/repo/git/gogs.ts @@ -39,11 +39,11 @@ export class GogsApi extends Repo { let owner = parsed.owner if ( owner == undefined ){ - debug.log("git owner extraction failed"); + this.logger.log("git owner extraction failed"); throw new Error("git owner extraction failed"); } if ( repo == undefined ){ - debug.log("git owner extraction failed"); + this.logger.log("git owner extraction failed"); throw new Error("git repo extraction failed"); } @@ -209,9 +209,9 @@ export class GogsApi extends Repo { debug.debug('Gogs webhook signature is valid for event: '+delivery); verified = true; } else { - debug.log('ERROR: invalid signature for event: '+delivery); - debug.log('Hash: '+hash); - debug.log('Signature: '+signature); + this.logger.log('ERROR: invalid signature for event: '+delivery); + this.logger.log('Hash: '+hash); + this.logger.log('Signature: '+signature); verified = false; return false; } @@ -296,7 +296,7 @@ export class GogsApi extends Repo { ret.push(branch.name) } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -305,7 +305,7 @@ export class GogsApi extends Repo { ret.push(tag.name) } } catch (error) { - debug.log(error) + this.logger.log(error) } try { @@ -314,7 +314,7 @@ export class GogsApi extends Repo { ret.push(commit.sha) } } catch (error) { - debug.log(error) + this.logger.log(error) } return ret; diff --git a/server-refactored-v3/src/repo/git/repo.ts b/server-refactored-v3/src/repo/git/repo.ts index dec96448..53030dc2 100644 --- a/server-refactored-v3/src/repo/git/repo.ts +++ b/server-refactored-v3/src/repo/git/repo.ts @@ -1,20 +1,20 @@ -import debug from 'debug'; +import { Logger } from "@nestjs/common"; import * as crypto from "crypto" -import sshpk from 'sshpk'; import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; import { IDeployKeyPair} from '../repo.interface'; -debug('app:kubero:git:repo') export abstract class Repo { protected repoProvider: string; + protected logger = new Logger(Repo.name); + protected sshpk = require('sshpk'); constructor(repoProvider: string) { this.repoProvider = repoProvider; } protected createDeployKeyPair(): IDeployKeyPair{ - debug.debug('createDeployKeyPair'); + this.logger.debug('createDeployKeyPair'); const keyPair = crypto.generateKeyPairSync('ed25519', { //modulusLength: 4096, @@ -29,14 +29,15 @@ export abstract class Repo { //passphrase: '' } }); - debug.debug(JSON.stringify(keyPair)); + this.logger.debug(JSON.stringify(keyPair)); - const pubKeySsh = sshpk.parseKey(keyPair.publicKey, 'pem'); + console.debug(this.sshpk); + const pubKeySsh = this.sshpk.parseKey(keyPair.publicKey, 'pem'); const pubKeySshString = pubKeySsh.toString('ssh'); const fingerprint = pubKeySsh.fingerprint('sha256').toString('hex'); console.debug(pubKeySshString); - const privKeySsh = sshpk.parsePrivateKey(keyPair.privateKey, 'pem'); + const privKeySsh = this.sshpk.parsePrivateKey(keyPair.privateKey, 'pem'); const privKeySshString = privKeySsh.toString('ssh'); console.debug(privKeySshString); @@ -50,14 +51,14 @@ export abstract class Repo { } public async connectRepo(gitrepo: string): Promise<{keys: IDeploykeyR | undefined, repository: IRepository, webhook: IWebhookR | undefined}> { - debug.log('connectPipeline: '+gitrepo); + this.logger.log('connectPipeline: '+gitrepo); if (process.env.KUBERO_WEBHOOK_SECRET == undefined) { - debug.log("KUBERO_WEBHOOK_SECRET is not defined") + this.logger.log("KUBERO_WEBHOOK_SECRET is not defined") throw new Error("KUBERO_WEBHOOK_SECRET is not defined"); } if (process.env.KUBERO_WEBHOOK_URL == undefined) { - debug.log("KUBERO_WEBHOOK_URL is not defined") + this.logger.log("KUBERO_WEBHOOK_URL is not defined") throw new Error("KUBERO_WEBHOOK_URL is not defined"); } @@ -107,7 +108,7 @@ export abstract class Repo { } public async disconnectRepo(gitrepo: string): Promise { - debug.log('disconnectPipeline: '+gitrepo); + this.logger.log('disconnectPipeline: '+gitrepo); const {owner, repo} = this.parseRepo(gitrepo); diff --git a/server-refactored-v3/src/repo/repo.controller.ts b/server-refactored-v3/src/repo/repo.controller.ts index 1bd77194..fbd0c7a7 100644 --- a/server-refactored-v3/src/repo/repo.controller.ts +++ b/server-refactored-v3/src/repo/repo.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { RepoService } from './repo.service'; import { ApiOperation } from '@nestjs/swagger'; @@ -48,4 +48,39 @@ export class RepoController { ) { return this.repoService.listReferences(provider, gitrepob64); } + + @Post('/:repoprovider/connect') + @ApiOperation({ summary: 'Connect a repository' }) + async connectRepo( + @Param('repoprovider') repoprovider: string, + @Body() body: any, + ) { + return this.repoService.connectRepo(repoprovider, body.gitrepo); + } + + @ApiOperation({ summary: 'Disconnect a repository' }) + @Post('/:repoprovider/disconnect') + async disconnectRepo( + @Param('repoprovider') repoprovider: string, + @Body() body: any, + ) { + return this.repoService.disconnectRepo(repoprovider, body.gitrepo); + } + + @ApiOperation({ summary: 'Start a Pull Request App' }) + @Post('/pullrequest/start') + async startPullRequest( + @Body() body: any, + ) { + return "Not implemented"; + //return this.repoService.startPullRequest(body); + } + + @ApiOperation({ summary: 'Webhooks endpoint for repository providers' }) + @Post('/repo/webhooks/:repoprovider') + async repositoryWebhook( + @Body() body: any, + ) { + return "Not implemented"; + } } diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 38c3c012..42bb90e2 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -404,6 +404,11 @@ "@eslint/core" "^0.10.0" levn "^0.4.1" +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" @@ -1159,6 +1164,22 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@nuxt/opencollective@0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.4.1.tgz#57bc41d2b03b2fba20b935c15950ac0f4bd2cea2" @@ -1400,6 +1421,11 @@ resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@tsconfig/node10@^1.0.7": version "1.0.11" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" @@ -1420,6 +1446,13 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/asn1@*": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@types/asn1/-/asn1-0.2.4.tgz#a0f89f9ddad8186c9c081c5df2e5cade855d2ac0" + integrity sha512-V91DSJ2l0h0gRhVP4oBfBzRBN9lAbPUkGDMCnwedqPKX2d84aAMc9CulOvxdw1f7DfEYx99afab+Rsm3e52jhA== + dependencies: + "@types/node" "*" + "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -1699,6 +1732,14 @@ "@types/node" "*" "@types/send" "*" +"@types/sshpk@^1.17.4": + version "1.17.4" + resolved "https://registry.yarnpkg.com/@types/sshpk/-/sshpk-1.17.4.tgz#239f86cc7f39c74285d4aea1cfee9fb3288f856f" + integrity sha512-5gI/7eJn6wmkuIuFY8JZJ1g5b30H9K5U5vKrvOuYu+hoZLb2xcVEgxhYZ2Vhbs0w/ACyzyfkJq0hQtBfSCugjw== + dependencies: + "@types/asn1" "*" + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -2082,13 +2123,28 @@ acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" +agentkeepalive@^4.1.3: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-formats@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" @@ -2210,6 +2266,14 @@ are-we-there-yet@^2.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -2423,6 +2487,13 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bitbucket@^2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/bitbucket/-/bitbucket-2.12.0.tgz#bb13796502c1d3ace0143fc01777140e7e18e78b" @@ -2434,7 +2505,7 @@ bitbucket@^2.12.0: node-fetch "^2.6.0" url-template "^2.0.8" -bl@^4.1.0: +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -2540,6 +2611,30 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -2657,6 +2752,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -2682,6 +2782,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -2751,7 +2856,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.2: +color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -2951,7 +3056,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -2984,6 +3089,11 @@ dedent@^1.0.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -3128,7 +3238,14 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -end-of-stream@^1.1.0: +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -3163,6 +3280,16 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -3364,6 +3491,11 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" @@ -3534,6 +3666,11 @@ file-type@^19.0.0, file-type@^19.6.0: token-types "^6.0.0" uint8array-extras "^1.3.0" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + filelist@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" @@ -3692,6 +3829,11 @@ fresh@^0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -3743,6 +3885,20 @@ gauge@^3.0.0: strip-ansi "^6.0.1" wide-align "^1.1.2" +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3829,6 +3985,11 @@ gitea-js@^1.23.0: resolved "https://registry.yarnpkg.com/gitea-js/-/gitea-js-1.23.0.tgz#44914028f4c4675ccb01ee2ac4041aedd0bab95a" integrity sha512-f4+UPoWgDetZeZ+Awo5iI1nVdO5bjxA8+2QCeLo3oYWUYxKyzLfXgbW1EPD635wb8hLgS0DRBu5XhtiuYKEeUA== +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3938,7 +4099,7 @@ got@^13.0.0: p-cancelable "^3.0.0" responselike "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -4003,7 +4164,7 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.1: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== @@ -4019,6 +4180,15 @@ http-errors@2.0.0, http-errors@^2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -4057,6 +4227,13 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + iconv-lite@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8" @@ -4064,7 +4241,7 @@ iconv-lite@0.5.2: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -4109,6 +4286,16 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -4122,6 +4309,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inspect-with-kind@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz#fce151d4ce89722c82ca8e9860bb96f9167c316c" @@ -4129,6 +4321,14 @@ inspect-with-kind@^1.0.5: dependencies: kind-of "^6.0.2" +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -4180,6 +4380,11 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -4723,6 +4928,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -4915,6 +5125,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + magic-string@0.30.12: version "0.30.12" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" @@ -4948,6 +5165,28 @@ make-error@^1.1.1, make-error@^1.3.6: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -5082,12 +5321,51 @@ minimatch@^9.0.3, minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^3.0.0: +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== @@ -5104,7 +5382,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -minizlib@^2.1.1: +minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -5120,6 +5398,11 @@ minizlib@^3.0.1: minipass "^7.0.4" rimraf "^5.0.5" +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -5127,7 +5410,7 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" -mkdirp@^1.0.3: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -5147,7 +5430,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.3: +ms@^2.0.0, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5170,6 +5453,11 @@ mute-stream@^2.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== +napi-build-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -5180,6 +5468,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^0.6.2: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + negotiator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" @@ -5190,6 +5483,13 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +node-abi@^3.3.0: + version "3.74.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.74.0.tgz#5bfb4424264eaeb91432d2adb9da23c63a301ed0" + integrity sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w== + dependencies: + semver "^7.3.5" + node-abort-controller@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" @@ -5200,6 +5500,11 @@ node-addon-api@^5.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + node-emoji@1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -5214,6 +5519,22 @@ node-fetch@^2.6.0, node-fetch@^2.6.7, node-fetch@^2.7.0: dependencies: whatwg-url "^5.0.0" +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -5263,6 +5584,16 @@ npmlog@^5.0.1: gauge "^3.0.0" set-blocking "^2.0.0" +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -5392,6 +5723,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -5583,6 +5921,24 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +prebuild-install@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^2.0.0" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -5621,6 +5977,19 @@ prometheus-query@^3.4.1: dependencies: axios "^1.6.0" +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -5723,6 +6092,16 @@ raw-body@^3.0.0: iconv-lite "0.6.3" unpipe "1.0.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -5741,7 +6120,7 @@ readable-stream@^2.2.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5866,6 +6245,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -6087,6 +6471,20 @@ signal-exit@^4.0.1, signal-exit@^4.1.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -6097,6 +6495,11 @@ slash@3.0.0, slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + socket.io-adapter@~2.5.2: version "2.5.5" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082" @@ -6126,6 +6529,23 @@ socket.io@4.8.1: socket.io-adapter "~2.5.2" socket.io-parser "~4.2.4" +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.4" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.4.tgz#07109755cdd4da03269bda4725baa061ab56d5cc" + integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + sort-keys-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" @@ -6166,12 +6586,29 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sshpk@^1.7.0: +sqlite3@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + +sshpk@^1.18.0, sshpk@^1.7.0: version "1.18.0" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== @@ -6186,6 +6623,13 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -6316,6 +6760,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strtok3@^9.0.1: version "9.1.1" resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-9.1.1.tgz#f8feb188b3fcdbf9b8819cc9211a824c3731df38" @@ -6391,6 +6840,27 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar-fs@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5" + integrity sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar-stream@^3.1.7: version "3.1.7" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" @@ -6400,7 +6870,7 @@ tar-stream@^3.1.7: fast-fifo "^1.2.0" streamx "^2.15.0" -tar@^6.1.11: +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== @@ -6685,6 +7155,20 @@ undici-types@~6.20.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz#52e7d0e9b3dc4df06cc33cb2b9fd79041a54827e" @@ -6837,14 +7321,14 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which@^2.0.1: +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -wide-align@^1.1.2: +wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== From c8ce39b555cbdb511fb30f63f4e88114d350bbd7 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Tue, 11 Feb 2025 23:51:09 +0100 Subject: [PATCH 31/65] move contexts to kubernetes subpath --- .../src/kubernetes/dto/kubernetes.dto.ts | 17 ++++++++++++++++- .../src/kubernetes/kubernetes.controller.ts | 14 ++++++++++++-- .../src/settings/settings.controller.ts | 7 ------- .../src/settings/settings.service.ts | 4 ---- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts b/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts index 18c14a13..16ea1930 100644 --- a/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts +++ b/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts @@ -14,4 +14,19 @@ export class StorageClassDTO { volumeBindingMode: string; //allowVolumeExpansion: boolean; //mountOptions: string[]; -} \ No newline at end of file +} + +export class ContextDTO { + + @ApiProperty() + cluster: string + + @ApiProperty() + name: string + + @ApiProperty() + user: string + + @ApiProperty() + namespace?: string +} diff --git a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts index ea3b6f2d..1a65013f 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Query } from '@nestjs/common'; import { KubernetesService } from './kubernetes.service'; import { ApiOperation, ApiOkResponse, ApiResponse } from '@nestjs/swagger'; -import { StorageClassDTO } from './dto/kubernetes.dto'; +import { StorageClassDTO, ContextDTO } from './dto/kubernetes.dto'; @Controller({ path: 'api/kubernetes', version: '1' }) export class KubernetesController { @@ -17,7 +17,7 @@ export class KubernetesController { } @ApiOkResponse({ - description: 'A List of available contexts', + description: 'A List of available storage classes', type: StorageClassDTO, isArray: true }) @@ -38,4 +38,14 @@ export class KubernetesController { return this.kubernetesService.getDomains(); } + @ApiOkResponse({ + description: 'A List of available contexts', + type: ContextDTO, + isArray: true + }) + @ApiOperation({ summary: 'Get available contexts' }) + @Get('/contexts') + async getContexts(): Promise { + return this.kubernetesService.getContexts(); + } } diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts index a8f3f191..89206792 100644 --- a/server-refactored-v3/src/settings/settings.controller.ts +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -26,13 +26,6 @@ export class SettingsController { return this.settingsService.getTemplateConfig(); } - // TODO: Move to kubernetes module - @ApiOperation({ summary: 'Get available contexts' }) - @Get('/contexts') - async getContexts() { - return this.settingsService.getContexts(); - } - @ApiOperation({ summary: 'Get the registry settings' }) @Get('/registry') async getRegistry() { diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 732de771..840d6d7b 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -291,10 +291,6 @@ export class SettingsService { return this.features.sleep } - public getContexts(): Context[] { - return this.kubectl.getContexts() - } - public async getRegistry(): Promise { const namespace = process.env.KUBERO_NAMESPACE || "kubero" let kuberoes = await this.kubectl.getKuberoConfig(namespace) From 833a77b62a80732742ce901769a47ce4e6b63d29 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 12 Feb 2025 08:10:27 +0100 Subject: [PATCH 32/65] Refactor console.log statements to use logger methods in Git-related classes and update API endpoint in pipeline form component. --- client/src/components/pipelines/form.vue | 2 +- server-refactored-v3/src/repo/git/bitbucket.ts | 11 +++++------ server-refactored-v3/src/repo/git/gitea.ts | 14 +++++++------- server-refactored-v3/src/repo/git/github.ts | 2 +- server-refactored-v3/src/repo/git/repo.ts | 9 ++++----- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/client/src/components/pipelines/form.vue b/client/src/components/pipelines/form.vue index 3919f911..eb01eb6b 100644 --- a/client/src/components/pipelines/form.vue +++ b/client/src/components/pipelines/form.vue @@ -713,7 +713,7 @@ export default defineComponent({ this.buildpack = buildpack; }, getContextList() { - axios.get('/api/settings/contexts').then(response => { + axios.get('/api/kubernetes/contexts').then(response => { for (let i = 0; i < response.data.length; i++) { this.contextList.push(response.data[i].name); } diff --git a/server-refactored-v3/src/repo/git/bitbucket.ts b/server-refactored-v3/src/repo/git/bitbucket.ts index ad9cc510..33822e24 100644 --- a/server-refactored-v3/src/repo/git/bitbucket.ts +++ b/server-refactored-v3/src/repo/git/bitbucket.ts @@ -47,14 +47,14 @@ export class BitbucketApi extends Repo { let repo = parsed.name let owner = parsed.owner - console.log(owner, repo); + //console.log(owner, repo); try { // https://bitbucketjs.netlify.app/#api-repositories-repositories_get let res = await this.bitbucket.repositories.get({ repo_slug: repo, workspace: owner }) - console.log(res.data); + //console.log(res.data); ret = { status: res.status, @@ -147,11 +147,10 @@ export class BitbucketApi extends Repo { } } } catch (e) { - console.log(e) + this.logger.error(e) } } else { - console.log("Webhook already exists") - console.log(webhook) + this.logger.debug("Webhook already exists") ret = { status: 422, @@ -199,7 +198,7 @@ export class BitbucketApi extends Repo { workspace: owner }); - console.log(res); + //console.log(res); ret = { diff --git a/server-refactored-v3/src/repo/git/gitea.ts b/server-refactored-v3/src/repo/git/gitea.ts index 1ab743c9..be450e37 100644 --- a/server-refactored-v3/src/repo/git/gitea.ts +++ b/server-refactored-v3/src/repo/git/gitea.ts @@ -38,7 +38,7 @@ export class GiteaApi extends Repo { let res = await this.gitea.repos.repoGet(owner, repo) .catch((error: any) => { - console.log(error) + this.logger.error(error) return ret; }) @@ -83,7 +83,7 @@ export class GiteaApi extends Repo { //https://try.gitea.io/api/swagger#/repository/repoListHooks const webhooksList = await this.gitea.repos.repoListHooks(owner, repo) .catch((error: any) => { - console.log(error) + this.logger.error(error) return ret; }) @@ -134,7 +134,7 @@ export class GiteaApi extends Repo { } } } catch (e) { - console.log(e) + this.logger.error(e) } return ret; } @@ -184,7 +184,7 @@ export class GiteaApi extends Repo { } } } catch (e) { - console.log(e) + this.logger.error(e) } return ret @@ -239,7 +239,7 @@ export class GiteaApi extends Repo { return webhook; } catch (error) { - console.log(error) + this.logger.error(error) return false; } } @@ -252,7 +252,7 @@ export class GiteaApi extends Repo { ret.push(repo.ssh_url) } } catch (error) { - console.log(error) + this.logger.error(error) } return ret; } @@ -268,7 +268,7 @@ export class GiteaApi extends Repo { ret.push(branch.name) } } catch (error) { - console.log(error) + this.logger.error(error) } return ret; diff --git a/server-refactored-v3/src/repo/git/github.ts b/server-refactored-v3/src/repo/git/github.ts index 8e8af676..f8cedc5f 100644 --- a/server-refactored-v3/src/repo/git/github.ts +++ b/server-refactored-v3/src/repo/git/github.ts @@ -147,7 +147,7 @@ export class GithubApi extends Repo { }) for (let webhook of existingWebhooksRes.data) { if (webhook.config.url === url) { - this.logger.log("Webhook already exists"); + this.logger.debug("Webhook already exists"); ret = { status: res.status, diff --git a/server-refactored-v3/src/repo/git/repo.ts b/server-refactored-v3/src/repo/git/repo.ts index 53030dc2..d2d0367b 100644 --- a/server-refactored-v3/src/repo/git/repo.ts +++ b/server-refactored-v3/src/repo/git/repo.ts @@ -29,17 +29,16 @@ export abstract class Repo { //passphrase: '' } }); - this.logger.debug(JSON.stringify(keyPair)); + //this.logger.debug(JSON.stringify(keyPair)); - console.debug(this.sshpk); const pubKeySsh = this.sshpk.parseKey(keyPair.publicKey, 'pem'); const pubKeySshString = pubKeySsh.toString('ssh'); const fingerprint = pubKeySsh.fingerprint('sha256').toString('hex'); - console.debug(pubKeySshString); + this.logger.debug(pubKeySshString); const privKeySsh = this.sshpk.parsePrivateKey(keyPair.privateKey, 'pem'); const privKeySshString = privKeySsh.toString('ssh'); - console.debug(privKeySshString); + //this.logger.debug(privKeySshString); return { fingerprint: fingerprint, @@ -63,7 +62,7 @@ export abstract class Repo { } const repository = await this.getRepository(gitrepo) - console.debug(repository); + //console.debug(repository); let keys: IDeploykeyR = { status: 500, From 81adf4ac7915c1fba2b53291b1505177f0def88d Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 12 Feb 2025 21:31:05 +0100 Subject: [PATCH 33/65] migrated Apps --- client/src/components/apps/form.vue | 22 +- client/src/components/pipelines/appcard.vue | 4 +- client/src/components/pipelines/prcard.vue | 2 +- .../src/apps/apps.controller.ts | 53 +++- server-refactored-v3/src/apps/apps.module.ts | 2 +- server-refactored-v3/src/apps/apps.service.ts | 268 +++++++++++++++++- server-refactored-v3/src/auth/auth.module.ts | 3 +- server-refactored-v3/src/dto/ok.dto.ts | 10 + .../src/kubernetes/dto/kubernetes.dto.ts | 78 ++++- .../src/kubernetes/kubernetes.controller.ts | 9 +- server-refactored-v3/src/logs/logs.module.ts | 2 - .../src/notifications/notifications.module.ts | 3 +- .../src/pipelines/dto/getPipeline.dto.ts | 138 +++++++++ .../src/pipelines/pipelines.controller.ts | 20 +- .../src/pipelines/pipelines.service.ts | 2 + .../src/repo/repo.controller.ts | 9 - .../src/security/security.module.ts | 4 +- .../src/settings/settings.module.ts | 4 +- server/src/kubero.ts | 3 + 19 files changed, 591 insertions(+), 45 deletions(-) create mode 100644 server-refactored-v3/src/dto/ok.dto.ts create mode 100644 server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts diff --git a/client/src/components/apps/form.vue b/client/src/components/apps/form.vue index 41403958..c2e3f095 100644 --- a/client/src/components/apps/form.vue +++ b/client/src/components/apps/form.vue @@ -51,12 +51,12 @@ md="6" > @@ -1284,7 +1284,7 @@
Addons
- + @@ -1609,7 +1609,7 @@ export default defineComponent({ deploymentstrategy: 'docker', phases: [] as Phase[], }, - appname: '', + name: '', resourceVersion: '', /* phases: [ @@ -1900,7 +1900,7 @@ export default defineComponent({ loadTemplate(template: string) { axios.get('/api/templates/'+template).then(response => { - this.appname = response.data.name; + this.name = response.data.name; this.containerPort = response.data.image.containerPort; this.deploymentstrategy = response.data.deploymentstrategy; @@ -2141,7 +2141,7 @@ export default defineComponent({ this.deploymentstrategy = response.data.spec.deploymentstrategy; this.buildstrategy = response.data.spec.buildstrategy || 'plain'; - this.appname = response.data.spec.name; + this.name = response.data.spec.name; this.sleep = response.data.spec.sleep; this.basicAuth = response.data.spec.basicAuth || { enabled: false, realm: 'Authentication required', accounts: [] }; this.buildpack = { @@ -2192,10 +2192,10 @@ export default defineComponent({ }, setSSL() { if (this.ingress.tls?.length == 0) { - this.ingress.tls = [{ hosts: [], secretName: this.appname+'-tls' }]; + this.ingress.tls = [{ hosts: [], secretName: this.name+'-tls' }]; } this.ingress.tls[0].hosts = []; - this.ingress.tls[0].secretName = this.appname+'-tls'; + this.ingress.tls[0].secretName = this.name+'-tls'; this.ingress.hosts.forEach((host, index) => { if (this.sslIndex[index]) { this.ingress.tls[0].hosts.push(host.host); @@ -2265,7 +2265,7 @@ export default defineComponent({ let postdata = { resourceVersion: this.resourceVersion, buildpack: this.buildpack, - appname: this.appname, + name: this.name, sleep: this.sleep, basicAuth: this.basicAuth, gitrepo: this.gitrepo, @@ -2366,7 +2366,7 @@ export default defineComponent({ pipeline: this.pipeline, buildpack: this.buildpack, phase: this.phase, - appname: this.appname.toLowerCase(), + name: this.name.toLowerCase(), sleep: this.sleep, basicAuth: this.basicAuth, gitrepo: this.gitrepo, @@ -2442,7 +2442,7 @@ export default defineComponent({ axios.post(`/api/apps`, postdata) // eslint-disable-next-line no-unused-vars .then(response => { - this.appname = ''; + this.name = ''; //console.log(response); this.$router.push({path: '/pipeline/' + this.pipeline + '/apps'}); }) diff --git a/client/src/components/pipelines/appcard.vue b/client/src/components/pipelines/appcard.vue index 0d50c5d3..448141a6 100644 --- a/client/src/components/pipelines/appcard.vue +++ b/client/src/components/pipelines/appcard.vue @@ -254,7 +254,7 @@ export default defineComponent({ }) .then((result) => { if (result.isConfirmed) { - axios.delete(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app.name}`) + axios.delete(`/api/apps/${this.pipeline}/${this.phase}/${this.app.name}`) .then(response => { //this.$router.push(`/pipeline/${this.pipeline}/apps`); //console.log("deleteApp"); @@ -269,7 +269,7 @@ export default defineComponent({ }); }, async restartApp() { - axios.get(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app.name}/restart`) + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app.name}/restart`) .then(response => { //console.log(response); this.loadingState = true; diff --git a/client/src/components/pipelines/prcard.vue b/client/src/components/pipelines/prcard.vue index 938eec2b..1e34d8a7 100644 --- a/client/src/components/pipelines/prcard.vue +++ b/client/src/components/pipelines/prcard.vue @@ -95,7 +95,7 @@ export default defineComponent({ //console.log("startReviewApp", this.pullrequest.number); this.loadingState = true; - axios.post("/api/repo/pullrequest/start", { + axios.post("/api/apps/pullrequest", { branch: this.pullrequest.branch, title: this.pullrequest.title, ssh_url: this.pullrequest.ssh_url, diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index 360b8f53..37400701 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -1,5 +1,7 @@ -import { Controller, Get, HttpCode, HttpStatus, Param } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post } from '@nestjs/common'; import { AppsService } from './apps.service'; +import { IUser } from '../auth/auth.interface'; +import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/apps', version: '1' }) export class AppsController { @@ -11,8 +13,53 @@ export class AppsController { async getApp( @Param('pipeline') pipeline: string, @Param('phase') phase: string, - @Param('app') appName: string, + @Param('app') app: string, ) { - return this.appsService.getApp(pipeline, phase, appName); + return this.appsService.getApp(pipeline, phase, app); } + + @Post('/') + @HttpCode(HttpStatus.CREATED) + async createApp( + @Body() app: any, + ) { + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890' + }; + return this.appsService.createApp(app, user); + } + + @Delete('/:pipeline/:phase/:app') + async deleteApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + ) { + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890' + }; + return this.appsService.deleteApp(pipeline, phase, app, user); + } + + @ApiOperation({ summary: 'Start a Pull Request App' }) + @Post('/pullrequest') + async startPullRequest( + @Body() body: any, + ) { + return this.appsService.createPRApp( + body.branch, + body.title, + body.ssh_url, + body.pipelineName, + ); + } + } diff --git a/server-refactored-v3/src/apps/apps.module.ts b/server-refactored-v3/src/apps/apps.module.ts index db28869b..3b0db5b0 100644 --- a/server-refactored-v3/src/apps/apps.module.ts +++ b/server-refactored-v3/src/apps/apps.module.ts @@ -4,7 +4,7 @@ import { KubernetesModule } from '../kubernetes/kubernetes.module'; import { AppsController } from './apps.controller'; import { PipelinesService } from '../pipelines/pipelines.service'; import { NotificationsService } from '../notifications/notifications.service'; -import { EventsGateway } from 'src/events/events.gateway'; +import { EventsGateway } from '../events/events.gateway'; @Module({ providers: [AppsService, KubernetesModule, PipelinesService, NotificationsService, EventsGateway], diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index f75646bc..24f04877 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -1,20 +1,30 @@ import { Injectable, Logger } from '@nestjs/common'; import { PipelinesService } from '../pipelines/pipelines.service'; import { KubernetesService } from '../kubernetes/kubernetes.service'; +import { NotificationsService } from '../notifications/notifications.service'; +import { IKubectlApp } from '../kubernetes/kubernetes.interface'; +import { INotification } from '../notifications/notifications.interface'; +import { App } from './app/app'; +import { IApp } from './apps.interface'; +import { IPipelineList } from '../pipelines/pipelines.interface'; +import { IUser } from '../auth/auth.interface'; +import { SettingsService } from 'src/settings/settings.service'; @Injectable() export class AppsService { - private Logger = new Logger(AppsService.name); + private logger = new Logger(AppsService.name); constructor( private kubectl: KubernetesService, - private pipelinesService: PipelinesService + private pipelinesService: PipelinesService, + private NotificationsService: NotificationsService, + private settingsService: SettingsService ) {} public async getApp(pipelineName: string, phaseName: string, appName: string) { - this.Logger.debug('get App: '+appName+' in '+ pipelineName+' phase: '+phaseName); + this.logger.debug('get App: '+appName+' in '+ pipelineName+' phase: '+phaseName); const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); if (contextName) { @@ -22,4 +32,256 @@ export class AppsService { return app; } } + + public async createApp(app: App, user: IUser) { + this.logger.debug('create App: '+app.name+' in '+ app.pipeline+' phase: '+app.phase + ' deploymentstrategy: '+app.deploymentstrategy); + + if ( process.env.KUBERO_READONLY == 'true'){ + this.logger.log('KUBERO_READONLY is set to true, not creating app ' + app.name); + return; + } + + const contextName = await this.pipelinesService.getContext(app.pipeline, app.phase); + if (contextName) { + await this.kubectl.createApp(app, contextName); + + if (app.deploymentstrategy == 'git' && (app.buildstrategy == 'dockerfile' || app.buildstrategy == 'nixpacks' || app.buildstrategy == 'buildpacks')){ + this.triggerImageBuild(app.pipeline, app.phase, app.name); + } + //this.appStateList.push(app); + + const m = { + 'name': 'newApp', + 'user': user.username, + 'resource': 'app', + 'action': 'create', + 'severity': 'normal', + 'message': 'Created new app: '+app.name+' in '+ app.pipeline+' phase: '+app.phase, + 'pipelineName':app.pipeline, + 'phaseName': app.phase, + 'appName': app.name, + 'data': { + 'app': app + } + } as INotification; + this.NotificationsService.send(m); + } + + } + + public async triggerImageBuild(pipeline: string, phase: string, appName: string) { + const contextName = await this.pipelinesService.getContext(pipeline, phase); + const namespace = pipeline+'-'+phase; + + const appresult = await this.getApp(pipeline, phase, appName) + + + const app = appresult as IKubectlApp; + let repo = ''; + + if (app.spec.gitrepo?.admin) { + repo = app.spec.gitrepo.ssh_url || ""; + } else { + repo = app.spec.gitrepo?.clone_url || ""; + } + + let dockerfilePath = 'Dockerfile'; + if (app.spec.buildstrategy === 'dockerfile') { + //dockerfilePath = app.spec.dockerfile || 'Dockerfile'; + } else if (app.spec.buildstrategy === 'nixpacks') { + dockerfilePath = '.nixpacks/Dockerfile'; + } + + + const timestamp = new Date().getTime(); + if (contextName) { + this.kubectl.setCurrentContext(contextName); + + this.kubectl.createBuildJob( + namespace, + appName, + pipeline, + app.spec.buildstrategy, + dockerfilePath, + { + url: repo, + ref: app.spec.branch, //git commit reference + }, + { + image: `${process.env.KUBERO_BUILD_REGISTRY}/${pipeline}/${appName}`, + tag: app.spec.branch+"-"+timestamp + } + ); + } + + return { + status: 'ok', + message: 'build started', + deploymentstrategy: app?.spec?.deploymentstrategy, + pipeline: pipeline, + phase: phase, + app: appName + }; + } + + // delete a app in a pipeline and phase + public async deleteApp(pipelineName: string, phaseName: string, appName: string, user: IUser) { + this.logger.debug('delete App: '+appName+' in '+ pipelineName+' phase: '+phaseName); + + if ( process.env.KUBERO_READONLY == 'true'){ + console.log('KUBERO_READONLY is set to true, not deleting app '+appName+' in '+ pipelineName+' phase: '+phaseName); + return; + } + + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + if (contextName) { + await this.kubectl.deleteApp(pipelineName, phaseName, appName, contextName); + //this.removeAppFromState(pipelineName, phaseName, appName); + + const m = { + 'name': 'deleteApp', + 'user': user.username, + 'resource': 'app', + 'action': 'delete', + 'severity': 'normal', + 'message': 'Deleted app: '+appName+' in '+ pipelineName+' phase: '+phaseName, + 'pipelineName':pipelineName, + 'phaseName': phaseName, + 'appName': appName, + 'data': {} + } as INotification; + this.NotificationsService.send(m); + } + } + + public async createPRApp(branch: string, title: string, ssh_url: string, pipelineName: string | undefined) { + + const podSizeList = await this.settingsService.getPodSizes(); + + if ( process.env.KUBERO_READONLY == 'true'){ + console.log('KUBERO_READONLY is set to true, not creating PR app '+title+' in '+ branch+' pipeline: '+pipelineName); + return; + } + + this.logger.debug('createPRApp: ', branch, title, ssh_url); + let pipelines = await this.pipelinesService.listPipelines() as IPipelineList; + + for (const pipeline of pipelines.items) { + console.log(pipeline.git.repository?.ssh_url, ssh_url); + console.log(pipeline.reviewapps); + + if (pipeline.reviewapps && + pipeline.git.repository && + pipeline.git.repository.ssh_url === ssh_url) { + + if (pipelineName && pipelineName != pipeline.name) { + continue; + } + + this.logger.debug('found pipeline: '+pipeline.name); + let pipelaneName = pipeline.name + let phaseName = 'review'; + let websaveTitle = title.toLowerCase().replace(/[^a-z0-9-]/g, '-'); //TODO improve websave title + + let appOptions:IApp = { + name: websaveTitle, + pipeline: pipelaneName, + sleep: 'disabled', //TODO use config value. This is BETA and should be disabled by default + gitrepo: pipeline.git.repository, + buildpack: pipeline.buildpack.name, + deploymentstrategy: pipeline.deploymentstrategy, + buildstrategy: 'plain', // TODO: use buildstrategy from pipeline + phase: phaseName, + branch: branch, + autodeploy: true, + podsize: podSizeList[0], //TODO select from podsizelist + autoscale: false, + basicAuth: { + enabled: false, + realm: '', + accounts: [] + }, + envVars: pipeline.phases.find(p => p.name == phaseName)?.defaultEnvvars || [], + extraVolumes: [], //TODO Not sure how to handlle extra Volumes on PR Apps + serviceAccount: { + annotations: {}, + create: false, + name: '' + }, + image: { + containerPort: 8080, //TODO use custom containerport + repository: pipeline.dockerimage, // FIXME: Maybe needs a lookup into buildpack + tag: "main", + command: [''], + pullPolicy: "Always", + fetch: pipeline.buildpack.fetch, + build: pipeline.buildpack.build, + run: pipeline.buildpack.run, + }, + web: { + replicaCount: 1, + autoscaling: { + minReplicas: 0, + maxReplicas: 0, + targetCPUUtilizationPercentage: 0, + targetMemoryUtilizationPercentage: 0 + } + }, + worker: { + replicaCount: 0, // TODO should be dynamic + autoscaling: { + minReplicas: 0, + maxReplicas: 0, + targetCPUUtilizationPercentage: 0, + targetMemoryUtilizationPercentage: 0 + } + }, + cronjobs: [], + addons: [], + resources: {}, + vulnerabilityscan: { + enabled: false, + schedule: "0 0 * * *", + image: { + repository: "aquasec/trivy", + tag: "latest" + } + }, + ingress: { + annotations: {}, + className: process.env.INGRESS_CLASSNAME || 'nginx', + enabled: true, + hosts: [ + { + host: websaveTitle+"."+pipeline.phases.find(p => p.name == phaseName)?.domain, + paths: [ + { + path: "/", + pathType: "Prefix" + } + ] + } + ], + tls: [] + }, + healthcheck: { + enabled: false, + path: "/", + startupSeconds: 90, + timeoutSeconds: 3, + periodSeconds: 10 + }, + } + let app = new App(appOptions); + + //TODO: Logad git user + const user = { + username: 'unknown' + } as IUser; + + this.createApp(app, user); + return { status: 'ok', message: 'app created '+app.name }; + } + } + } } diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index 3e849ed5..8a6893eb 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; import { KubernetesModule } from 'src/kubernetes/kubernetes.module'; -import { SettingsService } from '../settings/settings.service'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; import { AuthController } from './auth.controller'; @@ -10,7 +9,7 @@ import { AuditModule } from 'src/audit/audit.module'; @Module({ imports: [UsersModule, PassportModule ], - providers: [AuthService, LocalStrategy, KubernetesModule, AuditModule, SettingsService], + providers: [AuthService, LocalStrategy, KubernetesModule, AuditModule], controllers: [AuthController], }) export class AuthModule {} \ No newline at end of file diff --git a/server-refactored-v3/src/dto/ok.dto.ts b/server-refactored-v3/src/dto/ok.dto.ts new file mode 100644 index 00000000..ed594ba2 --- /dev/null +++ b/server-refactored-v3/src/dto/ok.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class OKDTO { + + @ApiProperty() + status: string; + + @ApiPropertyOptional() + message?: string; +} \ No newline at end of file diff --git a/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts b/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts index 16ea1930..8799e0e5 100644 --- a/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts +++ b/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class StorageClassDTO { @ApiProperty() @@ -27,6 +27,80 @@ export class ContextDTO { @ApiProperty() user: string - @ApiProperty() + @ApiPropertyOptional() namespace?: string } + +export class GetEventsDTO { + + @ApiProperty() + count: number + + @ApiProperty() + eventTime: any + + @ApiProperty() + firstTimestamp: string + + @ApiProperty() + involvedObject: { + apiVersion: string + kind: string + name: string + namespace: string + resourceVersion: string + uid: string + } + + @ApiProperty() + lastTimestamp: string + + @ApiProperty() + message: string + + @ApiProperty() + metadata: { + creationTimestamp: string + managedFields: Array<{ + apiVersion: string + fieldsType: string + fieldsV1: { + "f:count": {} + "f:firstTimestamp": {} + "f:involvedObject": {} + "f:lastTimestamp": {} + "f:message": {} + "f:reason": {} + "f:source": { + "f:component": {} + } + "f:type": {} + "f:reportingComponent"?: {} + } + manager: string + operation: string + time: string + }> + name: string + namespace: string + resourceVersion: string + uid: string + } + + @ApiProperty() + reason: string + + @ApiProperty() + reportingComponent: string + + @ApiProperty() + reportingInstance: string + + @ApiProperty() + source: { + component: string + } + + @ApiProperty() + type: string +} diff --git a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts index 1a65013f..4b4e2d04 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Query } from '@nestjs/common'; import { KubernetesService } from './kubernetes.service'; import { ApiOperation, ApiOkResponse, ApiResponse } from '@nestjs/swagger'; -import { StorageClassDTO, ContextDTO } from './dto/kubernetes.dto'; +import { StorageClassDTO, ContextDTO, GetEventsDTO } from './dto/kubernetes.dto'; @Controller({ path: 'api/kubernetes', version: '1' }) export class KubernetesController { @@ -9,7 +9,12 @@ export class KubernetesController { private readonly kubernetesService: KubernetesService ) {} - @ApiResponse({ status: 200, description: 'List of available contexts' }) + @ApiResponse({ + status: 200, + description: 'List of available contexts', + type: GetEventsDTO, + isArray: true + }) @ApiOperation({ summary: 'Get the Kubernetes events in a specific namespace' }) @Get('events') async getEvents(@Query('namespace') namespace: string) { diff --git a/server-refactored-v3/src/logs/logs.module.ts b/server-refactored-v3/src/logs/logs.module.ts index c53a2326..85d7a167 100644 --- a/server-refactored-v3/src/logs/logs.module.ts +++ b/server-refactored-v3/src/logs/logs.module.ts @@ -2,8 +2,6 @@ import { Module } from '@nestjs/common'; import { LogsService } from './logs.service'; import { EventsGateway } from '../events/events.gateway'; import { NotificationsService } from 'src/notifications/notifications.service'; -import { NotificationsModule } from 'src/notifications/notifications.module'; -import { EventsModule } from 'src/events/events.module'; import { AppsService } from 'src/apps/apps.service'; import { LogsController } from './logs.controller'; diff --git a/server-refactored-v3/src/notifications/notifications.module.ts b/server-refactored-v3/src/notifications/notifications.module.ts index 5f71678e..9cd3e9e9 100644 --- a/server-refactored-v3/src/notifications/notifications.module.ts +++ b/server-refactored-v3/src/notifications/notifications.module.ts @@ -1,9 +1,10 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { NotificationsService } from './notifications.service'; import { EventsGateway } from '../events/events.gateway'; import { AuditModule } from '../audit/audit.module'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; +@Global() @Module({ providers: [NotificationsService, EventsGateway, AuditModule, KubernetesModule], }) diff --git a/server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts b/server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts new file mode 100644 index 00000000..ffc649f4 --- /dev/null +++ b/server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts @@ -0,0 +1,138 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetPipelineDTO { + + @ApiProperty({ type: () => App, isArray: true }) + items: Array; +} + +class App { + @ApiProperty() + buildpack: { + build: { + command: string + readOnlyAppStorage?: boolean + repository: string + securityContext: { + allowPrivilegeEscalation: boolean + capabilities: { + add: Array + drop: Array + } + readOnlyRootFilesystem: boolean + runAsGroup: number + runAsNonRoot: boolean + runAsUser: number + } + tag: string + } + fetch: { + readOnlyAppStorage?: boolean + repository: string + securityContext: { + allowPrivilegeEscalation: boolean + capabilities: { + add: Array + drop: Array + } + readOnlyRootFilesystem: boolean + runAsGroup: number + runAsNonRoot: boolean + runAsUser: number + } + tag: string + } + language: string + name: string + run: { + command: string + readOnlyAppStorage: boolean + repository: string + securityContext: { + allowPrivilegeEscalation: boolean + capabilities: { + add: Array + drop: Array + } + readOnlyRootFilesystem: boolean + runAsGroup: number + runAsNonRoot: boolean + runAsUser: number + } + tag: string + } + } + + @ApiProperty() + buildstrategy: string + + @ApiProperty() + deploymentstrategy: string + + @ApiProperty() + dockerimage: string + + @ApiProperty() + domain: string + + @ApiProperty() + git: { + keys: { + priv: string + pub: string + created_at?: string + id?: number + read_only?: boolean + title?: string + url?: string + verified?: boolean + } + provider: string + repository: { + admin: boolean + clone_url: string + ssh_url: string + default_branch?: string + description?: string + homepage?: string + id?: number + language?: string + name?: string + node_id?: string + owner?: string + private?: boolean + push?: boolean + visibility?: string + } + webhook: { + active?: boolean + created_at?: string + events?: Array + id?: number + insecure?: string + url?: string + } + } + + @ApiProperty() + name: string + + @ApiProperty() + phases: Array<{ + context: string + defaultEnvvars: Array + domain: string + enabled: boolean + name: string + }> + + @ApiProperty() + registry: { + host: string + password: string + username: string + } + + @ApiProperty() + reviewapps: boolean +} diff --git a/server-refactored-v3/src/pipelines/pipelines.controller.ts b/server-refactored-v3/src/pipelines/pipelines.controller.ts index 20bec781..61bac83c 100644 --- a/server-refactored-v3/src/pipelines/pipelines.controller.ts +++ b/server-refactored-v3/src/pipelines/pipelines.controller.ts @@ -1,7 +1,9 @@ import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import { PipelinesService } from './pipelines.service'; -import { ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiOkResponse, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { CreatePipelineDTO } from './dto/replacePipeline.dto'; +import { GetPipelineDTO } from './dto/getPipeline.dto'; +import { OKDTO } from 'src/dto/ok.dto'; import { IUser } from '../auth/auth.interface'; import { IPipeline } from './pipelines.interface'; @@ -10,15 +12,25 @@ export class PipelinesController { constructor(private pipelinesService: PipelinesService) {} + @ApiOkResponse({ + description: 'A List of Pipelines', + type: GetPipelineDTO, + isArray: false + }) @ApiOperation({ summary: 'Get all pipelines' }) @Get('/') async getPipelines() { return this.pipelinesService.listPipelines(); } + @ApiOkResponse({ + description: 'A List of Pipelines', + type: OKDTO, + isArray: false + }) @ApiOperation({ summary: 'Create a new pipeline' }) @Post('/') - async createPipeline(@Body() pl: CreatePipelineDTO) { + async createPipeline(@Body() pl: CreatePipelineDTO): Promise { //TODO: Migration -> this is a mock user const user: IUser = { id: 1, @@ -39,10 +51,10 @@ export class PipelinesController { deploymentstrategy: pl.deploymentstrategy, buildstrategy: pl.buildstrategy, }; - return this.pipelinesService.createPipeline(pipeline, user); + return this.pipelinesService.createPipeline(pipeline, user) as Promise; } - @ApiOperation({ summary: 'Get a soecific pipeline' }) + @ApiOperation({ summary: 'Get a specific pipeline' }) @Get('/:pipeline') async getPipeline( @Param('pipeline') pipeline: string, diff --git a/server-refactored-v3/src/pipelines/pipelines.service.ts b/server-refactored-v3/src/pipelines/pipelines.service.ts index 1ac6a442..f9b3ffa3 100644 --- a/server-refactored-v3/src/pipelines/pipelines.service.ts +++ b/server-refactored-v3/src/pipelines/pipelines.service.ts @@ -211,6 +211,8 @@ export class PipelinesService { } } as INotification; this.notificationsService.send(m); + + return { 'status': 'ok', 'message': 'Pipeline created: '+pipeline.name }; } } diff --git a/server-refactored-v3/src/repo/repo.controller.ts b/server-refactored-v3/src/repo/repo.controller.ts index fbd0c7a7..4983dba6 100644 --- a/server-refactored-v3/src/repo/repo.controller.ts +++ b/server-refactored-v3/src/repo/repo.controller.ts @@ -67,15 +67,6 @@ export class RepoController { return this.repoService.disconnectRepo(repoprovider, body.gitrepo); } - @ApiOperation({ summary: 'Start a Pull Request App' }) - @Post('/pullrequest/start') - async startPullRequest( - @Body() body: any, - ) { - return "Not implemented"; - //return this.repoService.startPullRequest(body); - } - @ApiOperation({ summary: 'Webhooks endpoint for repository providers' }) @Post('/repo/webhooks/:repoprovider') async repositoryWebhook( diff --git a/server-refactored-v3/src/security/security.module.ts b/server-refactored-v3/src/security/security.module.ts index 956258e9..00b840ad 100644 --- a/server-refactored-v3/src/security/security.module.ts +++ b/server-refactored-v3/src/security/security.module.ts @@ -4,9 +4,11 @@ import { SecurityService } from './security.service'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; import { PipelinesModule } from '../pipelines/pipelines.module'; import { AppsService } from '../apps/apps.service'; +import { NotificationsService } from '../notifications/notifications.service'; +import { EventsGateway } from 'src/events/events.gateway'; @Module({ controllers: [SecurityController], - providers: [SecurityService, KubernetesModule, PipelinesModule, AppsService] + providers: [SecurityService, KubernetesModule, PipelinesModule, AppsService, NotificationsService, EventsGateway], }) export class SecurityModule {} diff --git a/server-refactored-v3/src/settings/settings.module.ts b/server-refactored-v3/src/settings/settings.module.ts index f735cce4..b9a21f9a 100644 --- a/server-refactored-v3/src/settings/settings.module.ts +++ b/server-refactored-v3/src/settings/settings.module.ts @@ -1,10 +1,12 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { SettingsController } from './settings.controller'; import { SettingsService } from './settings.service'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; +@Global() @Module({ controllers: [SettingsController], providers: [SettingsService, KubernetesModule], + exports: [SettingsService], }) export class SettingsModule {} diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 09dd8183..d1a849ba 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -339,6 +339,7 @@ export class Kubero { } + //Migrated tp apps // create a new app in a specified pipeline and phase public async newApp(app: App, user: User) { debug.log('create App: '+app.name+' in '+ app.pipeline+' phase: '+app.phase + ' deploymentstrategy: '+app.deploymentstrategy); @@ -677,6 +678,7 @@ export class Kubero { return apps; } + // Migrated to Apps // creates a PR App in all Pipelines that have review apps enabled and the same ssh_url private async createPRApp(branch: string, title: string, ssh_url: string, pipelineName: string | undefined) { @@ -1431,6 +1433,7 @@ export class Kubero { return summary; } + //Migrated to apps public async triggerImageBuild(pipeline: string, phase: string, appName: string) { const contextName = this.getContext(pipeline, phase); const namespace = pipeline+'-'+phase; From 79827e4a35322bf5f5b7d33f17aad796053e069b Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 12 Feb 2025 22:26:49 +0100 Subject: [PATCH 34/65] performace improvements --- server-refactored-v3/src/apps/apps.module.ts | 4 +--- server-refactored-v3/src/deployments/deployments.module.ts | 3 +-- server-refactored-v3/src/logs/logs.module.ts | 3 +-- .../src/notifications/notifications.module.ts | 1 + .../src/notifications/notifications.service.ts | 4 +++- server-refactored-v3/src/pipelines/pipelines.module.ts | 4 +--- server-refactored-v3/src/security/security.module.ts | 5 +---- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/server-refactored-v3/src/apps/apps.module.ts b/server-refactored-v3/src/apps/apps.module.ts index 3b0db5b0..b547b3f2 100644 --- a/server-refactored-v3/src/apps/apps.module.ts +++ b/server-refactored-v3/src/apps/apps.module.ts @@ -3,11 +3,9 @@ import { AppsService } from './apps.service'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; import { AppsController } from './apps.controller'; import { PipelinesService } from '../pipelines/pipelines.service'; -import { NotificationsService } from '../notifications/notifications.service'; -import { EventsGateway } from '../events/events.gateway'; @Module({ - providers: [AppsService, KubernetesModule, PipelinesService, NotificationsService, EventsGateway], + providers: [AppsService, KubernetesModule, PipelinesService], exports: [AppsService], controllers: [AppsController], }) diff --git a/server-refactored-v3/src/deployments/deployments.module.ts b/server-refactored-v3/src/deployments/deployments.module.ts index 01e45972..0c7754e5 100644 --- a/server-refactored-v3/src/deployments/deployments.module.ts +++ b/server-refactored-v3/src/deployments/deployments.module.ts @@ -3,11 +3,10 @@ import { DeploymentsController } from './deployments.controller'; import { DeploymentsService } from './deployments.service'; import { AppsService } from 'src/apps/apps.service'; import { EventsGateway } from 'src/events/events.gateway'; -import { NotificationsService } from 'src/notifications/notifications.service'; import { LogsService } from 'src/logs/logs.service'; @Module({ controllers: [DeploymentsController], - providers: [DeploymentsService, AppsService, EventsGateway, NotificationsService, LogsService] + providers: [DeploymentsService, AppsService, EventsGateway, LogsService] }) export class DeploymentsModule {} diff --git a/server-refactored-v3/src/logs/logs.module.ts b/server-refactored-v3/src/logs/logs.module.ts index 85d7a167..cf25415c 100644 --- a/server-refactored-v3/src/logs/logs.module.ts +++ b/server-refactored-v3/src/logs/logs.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; import { LogsService } from './logs.service'; import { EventsGateway } from '../events/events.gateway'; -import { NotificationsService } from 'src/notifications/notifications.service'; import { AppsService } from 'src/apps/apps.service'; import { LogsController } from './logs.controller'; @Module({ - providers: [LogsService, EventsGateway, NotificationsService, AppsService], + providers: [LogsService, EventsGateway, AppsService], controllers: [LogsController] }) export class LogsModule {} diff --git a/server-refactored-v3/src/notifications/notifications.module.ts b/server-refactored-v3/src/notifications/notifications.module.ts index 9cd3e9e9..f8568872 100644 --- a/server-refactored-v3/src/notifications/notifications.module.ts +++ b/server-refactored-v3/src/notifications/notifications.module.ts @@ -7,5 +7,6 @@ import { KubernetesModule } from '../kubernetes/kubernetes.module'; @Global() @Module({ providers: [NotificationsService, EventsGateway, AuditModule, KubernetesModule], + exports: [NotificationsService], }) export class NotificationsModule {} diff --git a/server-refactored-v3/src/notifications/notifications.service.ts b/server-refactored-v3/src/notifications/notifications.service.ts index 879d3108..5b5cd190 100644 --- a/server-refactored-v3/src/notifications/notifications.service.ts +++ b/server-refactored-v3/src/notifications/notifications.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { AuditService } from 'src/audit/audit.service'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { INotificationConfig, INotification, INotificationSlack, INotificationWebhook, INotificationDiscord } from './notifications.interface'; @@ -12,6 +12,7 @@ export class NotificationsService { //public kubectl: Kubectl; //private audit: Audit; private config: IKuberoConfig; + private logger = new Logger(NotificationsService.name); constructor( private eventsGateway: EventsGateway, @@ -19,6 +20,7 @@ export class NotificationsService { private kubectl: KubernetesService, ) { this.config = {} as IKuberoConfig; + this.logger.log('NotificationsService initialized'); } public setConfig(config: IKuberoConfig) { diff --git a/server-refactored-v3/src/pipelines/pipelines.module.ts b/server-refactored-v3/src/pipelines/pipelines.module.ts index 93f5d7e2..263de029 100644 --- a/server-refactored-v3/src/pipelines/pipelines.module.ts +++ b/server-refactored-v3/src/pipelines/pipelines.module.ts @@ -1,13 +1,11 @@ import { Global, Module } from '@nestjs/common'; import { PipelinesController } from './pipelines.controller'; import { PipelinesService } from './pipelines.service'; -import { NotificationsService } from '../notifications/notifications.service'; -import { EventsGateway } from 'src/events/events.gateway'; @Global() @Module({ controllers: [PipelinesController], - providers: [PipelinesService, NotificationsService, EventsGateway], + providers: [PipelinesService], exports: [PipelinesService], }) export class PipelinesModule {} diff --git a/server-refactored-v3/src/security/security.module.ts b/server-refactored-v3/src/security/security.module.ts index 00b840ad..c0b18d67 100644 --- a/server-refactored-v3/src/security/security.module.ts +++ b/server-refactored-v3/src/security/security.module.ts @@ -4,11 +4,8 @@ import { SecurityService } from './security.service'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; import { PipelinesModule } from '../pipelines/pipelines.module'; import { AppsService } from '../apps/apps.service'; -import { NotificationsService } from '../notifications/notifications.service'; -import { EventsGateway } from 'src/events/events.gateway'; - @Module({ controllers: [SecurityController], - providers: [SecurityService, KubernetesModule, PipelinesModule, AppsService, NotificationsService, EventsGateway], + providers: [SecurityService, KubernetesModule, PipelinesModule, AppsService], }) export class SecurityModule {} From e272755f345d71dcf4663d763d59ea098265c86f Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 12 Feb 2025 22:52:08 +0100 Subject: [PATCH 35/65] migration fix PR App cards match --- client/src/components/pipelines/detail.vue | 1 + server-refactored-v3/src/app.module.ts | 2 -- server-refactored-v3/src/apps/apps.controller.ts | 2 +- server-refactored-v3/src/config/config.module.ts | 4 ---- 4 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 server-refactored-v3/src/config/config.module.ts diff --git a/client/src/components/pipelines/detail.vue b/client/src/components/pipelines/detail.vue index 88e5edbb..54b7845c 100644 --- a/client/src/components/pipelines/detail.vue +++ b/client/src/components/pipelines/detail.vue @@ -136,6 +136,7 @@ async function loadPullrequests() { response.data.forEach((pr: Pullrequest) => { let found = false; phases.value[0].apps.forEach((app: App) => { + console.log(app.name, pr.branch); if (app.name == pr.branch) { found = true; } diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index f7eaa333..7109ebb5 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -7,7 +7,6 @@ import { ServeStaticModule } from '@nestjs/serve-static'; import { AuthModule } from './auth/auth.module'; import { AppsModule } from './apps/apps.module'; import { PipelinesModule } from './pipelines/pipelines.module'; -import { ConfigModule } from './config/config.module'; import { RepoModule } from './repo/repo.module'; import { SettingsModule } from './settings/settings.module'; import { MetricsModule } from './metrics/metrics.module'; @@ -31,7 +30,6 @@ import { SecurityModule } from './security/security.module'; AuthModule, AppsModule, PipelinesModule, - ConfigModule, RepoModule, SettingsModule, MetricsModule, diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index 37400701..bd4dc98e 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -56,7 +56,7 @@ export class AppsController { ) { return this.appsService.createPRApp( body.branch, - body.title, + body.branch, body.ssh_url, body.pipelineName, ); diff --git a/server-refactored-v3/src/config/config.module.ts b/server-refactored-v3/src/config/config.module.ts deleted file mode 100644 index 19483700..00000000 --- a/server-refactored-v3/src/config/config.module.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Module } from '@nestjs/common'; - -@Module({}) -export class ConfigModule {} From 5ce9f8514b94b5370c28a35a9a954e4c34d5bab4 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 12 Feb 2025 23:16:27 +0100 Subject: [PATCH 36/65] Migrate Template Download --- .../src/apps/apps.controller.ts | 10 ++ server-refactored-v3/src/apps/apps.service.ts | 17 ++- .../src/templates/templates/templates.spec.ts | 7 ++ .../src/templates/templates/templates.ts | 109 ++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 server-refactored-v3/src/templates/templates/templates.spec.ts create mode 100644 server-refactored-v3/src/templates/templates/templates.ts diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index bd4dc98e..2459e391 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -62,4 +62,14 @@ export class AppsController { ); } + @ApiOperation({ summary: 'Download the app templates' }) + @Get('/:pipeline/:phase/:app/download') + async downloadAppTemplates( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + ) { + return this.appsService.getTemplate(pipeline, phase, app); + } + } diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index 24f04877..f232e894 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -8,13 +8,16 @@ import { App } from './app/app'; import { IApp } from './apps.interface'; import { IPipelineList } from '../pipelines/pipelines.interface'; import { IUser } from '../auth/auth.interface'; -import { SettingsService } from 'src/settings/settings.service'; +import { SettingsService } from 'src/settings/settings.service'; +//import YAML from 'yaml'; +import { KubectlTemplate } from 'src/templates/template'; @Injectable() export class AppsService { private logger = new Logger(AppsService.name); + private YAML = require('yaml'); constructor( private kubectl: KubernetesService, @@ -284,4 +287,16 @@ export class AppsService { } } } + + public async getTemplate(pipelineName: string, phaseName: string, appName: string ) { + const app = await this.getApp(pipelineName, phaseName, appName); + + const a = app as IKubectlApp; + let t = new KubectlTemplate(a.spec as IApp); + + //Convert template to Yaml + const template = this.YAML.stringify(t, {indent: 4, resolveKnownTags: true}); + + return template + } } diff --git a/server-refactored-v3/src/templates/templates/templates.spec.ts b/server-refactored-v3/src/templates/templates/templates.spec.ts new file mode 100644 index 00000000..aa72df12 --- /dev/null +++ b/server-refactored-v3/src/templates/templates/templates.spec.ts @@ -0,0 +1,7 @@ +import { Templates } from './templates'; + +describe('Templates', () => { + it('should be defined', () => { + expect(new Templates()).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/templates/templates/templates.ts b/server-refactored-v3/src/templates/templates/templates.ts new file mode 100644 index 00000000..0bc26816 --- /dev/null +++ b/server-refactored-v3/src/templates/templates/templates.ts @@ -0,0 +1,109 @@ +import { IApp, ICronjob, IExtraVolume } from "../../apps/apps.interface"; +import { ITemplate, IKubectlTemplate } from "../templates.interface"; +import { IKubectlMetadata } from "../../kubernetes/kubernetes.interface" +import { IAddon } from "src/addons/addons.interface"; + +export class KubectlTemplate implements IKubectlTemplate{ + apiVersion: string; + kind: string; + metadata: IKubectlMetadata; + spec: Template; + + constructor(app: IApp) { + this.apiVersion = "application.kubero.dev/v1alpha1"; + this.kind = "KuberoApp"; + this.metadata = { + name: app.name, + annotations: { + 'kubero.dev/template.architecture': '[]', + 'kubero.dev/template.description': '', + 'kubero.dev/template.icon': '', + 'kubero.dev/template.installation': '', + 'kubero.dev/template.links': '[]', + 'kubero.dev/template.screenshots': '[]', + 'kubero.dev/template.source': '', + 'kubero.dev/template.categories': '[]', + 'kubero.dev/template.title': '', + 'kubero.dev/template.website': '' + }, + labels: { + manager: 'kubero', + } + } + this.spec = new Template(app); + } +} + +class Template implements ITemplate{ + public name: string + public deploymentstrategy: 'git' | 'docker' + public envVars: {}[] = [] + /* + public serviceAccount: { + annotations: Object + create: boolean, + name: string, + }; + */ + public extraVolumes: IExtraVolume[] = [] + public cronjobs: ICronjob[] = [] + public addons: IAddon[] = [] + + public web: { + replicaCount: number + } + + public worker: { + replicaCount: number + } + + public image: { + containerPort: number, + pullPolicy?: 'Always', + repository: string, + tag: string, + /* + run: { + repository: string, + tag: string, + readOnlyAppStorage?: boolean, + securityContext: ISecurityContext + } + */ + }; + constructor( + app: IApp + ) { + this.name = app.name + this.deploymentstrategy = app.deploymentstrategy + + this.envVars = app.envVars + + //this.serviceAccount = app.serviceAccount; + + this.extraVolumes = app.extraVolumes + + this.cronjobs = app.cronjobs + + this.addons = app.addons + + this.web = { + replicaCount: app.web.replicaCount + } + this.worker = { + replicaCount: app.worker.replicaCount + } + + this.image = { + containerPort: app.image.containerPort, + pullPolicy: 'Always', + repository: app.image.repository || 'ghcr.io/kubero-dev/idler', + tag: app.image.tag || 'v1', + //run: app.image.run, + } + + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + //this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) + } +} \ No newline at end of file From b412b1ad7b15d896b0f10855cd37ac49e661f0ec Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 12 Feb 2025 23:30:45 +0100 Subject: [PATCH 37/65] Migrate app restart --- client/src/components/apps/detail.vue | 6 ++-- .../src/apps/apps.controller.ts | 18 ++++++++++++ server-refactored-v3/src/apps/apps.service.ts | 29 +++++++++++++++++++ server/src/kubero.ts | 3 ++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/client/src/components/apps/detail.vue b/client/src/components/apps/detail.vue index 475623ee..d05a6a2f 100644 --- a/client/src/components/apps/detail.vue +++ b/client/src/components/apps/detail.vue @@ -172,7 +172,7 @@ export default defineComponent({ this.$router.push(`/pipeline/${this.pipeline}/${this.phase}/apps/${this.app}`); }, ActionStartDownload() { - axios.get('/api/pipelines/'+this.pipeline+'/'+this.phase+'/'+this.app+'/download').then(response => { + axios.get('/api/apps/'+this.pipeline+'/'+this.phase+'/'+this.app+'/download').then(response => { //console.log(response.data); const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); @@ -197,7 +197,7 @@ export default defineComponent({ }) .then((result) => { if (result.isConfirmed) { - axios.delete(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}`) + axios.delete(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`) .then(response => { // sleep 1 second setTimeout(() => { @@ -213,7 +213,7 @@ export default defineComponent({ }); }, async restartApp() { - axios.get(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app}/restart`) + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/restart`) .then(response => { //console.log(response); this.loadingState = true; diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index 2459e391..cc076f54 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -72,4 +72,22 @@ export class AppsController { return this.appsService.getTemplate(pipeline, phase, app); } + @ApiOperation({ summary: 'Restart/Reload an app' }) + @Get('/:pipeline/:phase/:app/restart') + async restartApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + ) { + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890' + }; + + return this.appsService.restartApp(pipeline, phase, app, user); + } + } diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index f232e894..61a3fc13 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -299,4 +299,33 @@ export class AppsService { return template } + + public async restartApp(pipelineName: string, phaseName: string, appName: string, user: IUser) { + + if ( process.env.KUBERO_READONLY == 'true'){ + console.log('KUBERO_READONLY is set to true, not restarting app'+appName+' in '+ pipelineName+' phase: '+phaseName); + return; + } + + this.logger.debug('restart App: '+appName+' in '+ pipelineName+' phase: '+phaseName); + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + if (contextName) { + this.kubectl.restartApp(pipelineName, phaseName, appName, 'web', contextName); + this.kubectl.restartApp(pipelineName, phaseName, appName, 'worker', contextName); + + const m = { + 'name': 'restartApp', + 'user': user.username, + 'resource': 'app', + 'action': 'restart', + 'severity': 'normal', + 'message': 'Restarted app: '+appName+' in '+ pipelineName+' phase: '+phaseName, + 'pipelineName': pipelineName, + 'phaseName': phaseName, + 'appName': appName, + 'data': {} + } as INotification; + this.NotificationsService.send(m); + } + } } diff --git a/server/src/kubero.ts b/server/src/kubero.ts index d1a849ba..10453779 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -414,6 +414,7 @@ export class Kubero { } } + //Migrated to apps // delete a app in a pipeline and phase public async deleteApp(pipelineName: string, phaseName: string, appName: string, user: User) { debug.debug('delete App: '+appName+' in '+ pipelineName+' phase: '+phaseName); @@ -467,6 +468,7 @@ export class Kubero { } } + //Migrated to templates public async getTemplate(pipelineName: string, phaseName: string, appName: string ) { const app = await this.getApp(pipelineName, phaseName, appName); @@ -517,6 +519,7 @@ export class Kubero { return pipeline; } + //Migrated to apps public restartApp(pipelineName: string, phaseName: string, appName: string, user: User) { if ( process.env.KUBERO_READONLY == 'true'){ From 644972fb02907771fc0da809f7e5555545453bf6 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 13 Feb 2025 01:19:38 +0100 Subject: [PATCH 38/65] improve code structure --- client/src/components/apps/form.vue | 59 +++++++++++-------- client/src/components/pipelines/form.vue | 2 +- .../src/apps/apps.controller.ts | 28 ++++++++- server-refactored-v3/src/apps/apps.service.ts | 9 ++- .../src/kubernetes/kubernetes.interface.ts | 7 +++ .../src/pipelines/pipelines.controller.ts | 17 +++++- .../src/repo/repo.controller.ts | 14 ++--- 7 files changed, 99 insertions(+), 37 deletions(-) diff --git a/client/src/components/apps/form.vue b/client/src/components/apps/form.vue index c2e3f095..eebf15a1 100644 --- a/client/src/components/apps/form.vue +++ b/client/src/components/apps/form.vue @@ -64,6 +64,7 @@ cols="12" md="2" > + @@ -566,7 +568,7 @@ md="6" > @@ -881,7 +883,7 @@ Environment Variables - + Update + @@ -1490,7 +1494,7 @@ export default defineComponent({ return { breadcrumbItems: [ { - title: 'dashboard.-', + title: 'dashboard.pipelines', disabled: false, to: { name: 'Pipelines', params: {}} }, @@ -1643,7 +1647,7 @@ export default defineComponent({ }, autodeploy: true, sslIndex: [] as (boolean|undefined)[], - envvars: [ + envVars: [ //{ name: '', value: '' }, ] as EnvVar[], sAAnnotations: [ @@ -1711,7 +1715,6 @@ export default defineComponent({ letsecryptClusterIssuer: 'letsencrypt-prod', // deprecated in version 1.11.0 security: { - vulnerabilityScans: false, allowPrivilegeEscalation: false, runAsNonRoot: false, readOnlyRootFilesystem: true, @@ -1789,6 +1792,14 @@ export default defineComponent({ 'SYSLOG', 'WAKE_ALARM', ], + vulnerabilityscan: { + enabled: false, + schedule: "0 0 * * *", + image: { + repository: 'aquasec/trivy', + tag: 'latest', + }, + }, healthcheck: { enabled: true, path: '/', @@ -1907,7 +1918,7 @@ export default defineComponent({ this.docker.image = response.data.image.repository; this.docker.tag = response.data.image.tag; - this.envvars = response.data.envVars; + this.envVars = response.data.envVars; if (response.data.serviceAccount && response.data.serviceAccount.annotations) { this.sAAnnotations = Object.entries(response.data.serviceAccount.annotations).map(([key, value]) => ({annotation: key, value: value as string})); } @@ -1929,7 +1940,7 @@ export default defineComponent({ } // Open Panel if there is some data to show - if (this.envvars.length > 0) { + if (this.envVars.length > 0) { this.panel.push(6) } if (Object.keys(this.sAAnnotations).length > 0) { @@ -1986,12 +1997,12 @@ export default defineComponent({ // extract defaultEnvvars from pipeline phase for (let i = 0; i < this.pipelineData.phases.length; i++) { if (this.pipelineData.phases[i].name == this.phase) { - this.envvars = this.pipelineData.phases[i].defaultEnvvars; + this.envVars = this.pipelineData.phases[i].defaultEnvvars; } } // Open Panel if there is some data to show - if (this.envvars.length > 0) { + if (this.envVars.length > 0) { this.panel.push(6) } @@ -2086,7 +2097,7 @@ export default defineComponent({ }, loadBuildpacks() { - axios.get('/api/settings/buildpacks').then(response => { + axios.get('/api/settings/runpacks').then(response => { for (let i = 0; i < response.data.length; i++) { this.buildpacks.push({ text: response.data[i].name, @@ -2156,7 +2167,7 @@ export default defineComponent({ this.docker.tag = response.data.spec.image.tag || 'latest'; this.docker.command = command; this.autodeploy = response.data.spec.autodeploy; - this.envvars = response.data.spec.envVars; + this.envVars = response.data.spec.envVars; this.serviceAccount = response.data.spec.serviceAccount; if (response.data.spec.serviceAccount && response.data.spec.serviceAccount.annotations) { this.sAAnnotations = Object.entries(response.data.spec.serviceAccount.annotations).map(([key, value]) => ({annotation: key, value: value as string})); @@ -2171,7 +2182,7 @@ export default defineComponent({ this.workerreplicasrange = [response.data.spec.worker.autoscaling.minReplicas, response.data.spec.worker.autoscaling.maxReplicas]; this.cronjobs = this.cronjobUnformat(response.data.spec.cronjobs) || []; this.addons= response.data.spec.addons || []; - this.security.vulnerabilityScans = response.data.spec.vulnerabilityscan.enabled; + this.vulnerabilityscan = response.data.spec.vulnerabilityscan; this.ingress = response.data.spec.ingress || {}; this.healthcheck = response.data.spec.healthcheck || { enabled: true, path: '/', startupSeconds: 90, timeoutSeconds: 30, periodSeconds: 10 }; @@ -2273,7 +2284,7 @@ export default defineComponent({ deploymentstrategy: this.deploymentstrategy, buildstrategy: this.buildstrategy, image : { - containerport: this.containerPort, + containerPort: this.containerPort, repository: this.docker.image, tag: this.docker.tag, command: command, @@ -2282,7 +2293,7 @@ export default defineComponent({ run: this.buildpack?.run, }, autodeploy: this.autodeploy, - envvars: this.envvars, + envVars: this.envVars, // loop through serviceaccount annotations and convert to object serviceAccount: { annotations: this.sAAnnotations.reduce((acc, cur) => { @@ -2315,6 +2326,7 @@ export default defineComponent({ addons: this.addons, security: this.security, ingress: this.ingress, + vulnerabilityscan: this.vulnerabilityscan, healthcheck: this.healthcheck, } @@ -2374,7 +2386,7 @@ export default defineComponent({ deploymentstrategy: this.deploymentstrategy, buildstrategy: this.buildstrategy, image : { - containerport: this.containerPort, + containerPort: this.containerPort, repository: this.docker.image, tag: this.docker.tag, fetch: this.buildpack?.fetch, @@ -2382,7 +2394,7 @@ export default defineComponent({ run: this.buildpack?.run, }, autodeploy: this.autodeploy, - envvars: this.envvars, + envVars: this.envVars, serviceAccount: { annotations: this.sAAnnotations.reduce((acc, cur) => { acc[cur.annotation] = cur.value; @@ -2414,6 +2426,7 @@ export default defineComponent({ addons: this.addons, security: this.security, ingress: this.ingress, + vulnerabilityscan: this.vulnerabilityscan, healthcheck: this.healthcheck, } @@ -2439,7 +2452,7 @@ export default defineComponent({ }, } */ - axios.post(`/api/apps`, postdata) + axios.post(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`, postdata) // eslint-disable-next-line no-unused-vars .then(response => { this.name = ''; @@ -2464,15 +2477,15 @@ export default defineComponent({ } }, addEnvLine() { - this.envvars.push({ + this.envVars.push({ name: '', value: '', }); }, removeEnvLine(index: string) { - for (let i = 0; i < this.envvars.length; i++) { - if (this.envvars[i].name === index) { - this.envvars.splice(i, 1); + for (let i = 0; i < this.envVars.length; i++) { + if (this.envVars[i].name === index) { + this.envVars.splice(i, 1); } } }, @@ -2509,8 +2522,8 @@ export default defineComponent({ const [name, value] = line.split('='); // check if name isn't commented out if (name && !name.startsWith('#') && value) { - if (!this.envvars.some(envvar => envvar.name === name.trim())) { - this.envvars.push({ name: name.trim(), value: value.trim() }); + if (!this.envVars.some(envVars => envVars.name === name.trim())) { + this.envVars.push({ name: name.trim(), value: value.trim() }); } } } diff --git a/client/src/components/pipelines/form.vue b/client/src/components/pipelines/form.vue index eb01eb6b..8a531fb9 100644 --- a/client/src/components/pipelines/form.vue +++ b/client/src/components/pipelines/form.vue @@ -939,7 +939,7 @@ export default defineComponent({ this.buildpack = this.buildpackList[0].value; } - axios.post(`/api/pipelines`, { + axios.post(`/api/pipelines/${this.pipeline}`, { pipelineName: this.pipelineName, domain: this.domain, gitrepo: this.gitrepo, diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index cc076f54..77866aff 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -1,4 +1,5 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Logger, Param, Post, Res } from '@nestjs/common'; +import { Response } from 'express'; import { AppsService } from './apps.service'; import { IUser } from '../auth/auth.interface'; import { ApiOperation } from '@nestjs/swagger'; @@ -9,6 +10,7 @@ export class AppsController { private readonly appsService: AppsService, ) {} + @ApiOperation({ summary: 'Get app informations from a specific app' }) @Get('/:pipeline/:phase/:app') async getApp( @Param('pipeline') pipeline: string, @@ -18,11 +20,32 @@ export class AppsController { return this.appsService.getApp(pipeline, phase, app); } - @Post('/') + @ApiOperation({ summary: 'Create an app' }) + @Post('/:pipeline/:phase/:app') @HttpCode(HttpStatus.CREATED) async createApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') appName: string, @Body() app: any, ) { + + if (appName !== 'new') { + const msg = 'App name does not match the URL'; + Logger.error(msg); + throw new HttpException(msg, HttpStatus.BAD_REQUEST); + } + if (app.pipeline !== pipeline) { + const msg = 'Pipeline name does not match the URL'; + Logger.error(msg); + throw new HttpException(msg, HttpStatus.BAD_REQUEST); + } + if (app.phase !== phase) { + const msg = 'Phase name does not match the URL'; + Logger.error(msg); + throw new HttpException(msg, HttpStatus.BAD_REQUEST); + } + //TODO: Migration -> this is a mock user const user: IUser = { id: 1, @@ -33,6 +56,7 @@ export class AppsController { return this.appsService.createApp(app, user); } + @ApiOperation({ summary: 'Delete an app' }) @Delete('/:pipeline/:phase/:app') async deleteApp( @Param('pipeline') pipeline: string, diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index 61a3fc13..478ecacf 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -31,8 +31,15 @@ export class AppsService { const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); if (contextName) { + try { let app = await this.kubectl.getApp(pipelineName, phaseName, appName, contextName); - return app; + app.metadata.managedFields = [{}]; + app.status.deployedRelease = undefined; + return app; + } catch (error) { + this.logger.error('getApp error: '+error); + return null; + } } } diff --git a/server-refactored-v3/src/kubernetes/kubernetes.interface.ts b/server-refactored-v3/src/kubernetes/kubernetes.interface.ts index 493f0699..b59522db 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.interface.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.interface.ts @@ -46,6 +46,13 @@ export interface IKubectlApp kind: string; metadata: IKubectlMetadata spec: IApp ; + status: { + conditions: [Array: Object]; + deployedRelease?: { + name: string; + manifest: string; + } + } } export interface IStorageClass { diff --git a/server-refactored-v3/src/pipelines/pipelines.controller.ts b/server-refactored-v3/src/pipelines/pipelines.controller.ts index 61bac83c..04f9380d 100644 --- a/server-refactored-v3/src/pipelines/pipelines.controller.ts +++ b/server-refactored-v3/src/pipelines/pipelines.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Logger, Param, Post, Put } from '@nestjs/common'; import { PipelinesService } from './pipelines.service'; import { ApiOkResponse, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { CreatePipelineDTO } from './dto/replacePipeline.dto'; @@ -29,8 +29,19 @@ export class PipelinesController { isArray: false }) @ApiOperation({ summary: 'Create a new pipeline' }) - @Post('/') - async createPipeline(@Body() pl: CreatePipelineDTO): Promise { + @Post('/:pipeline') + @HttpCode(HttpStatus.CREATED) + async createPipeline( + @Param('pipeline') pipelineName: string, + @Body() pl: CreatePipelineDTO + ): Promise { + + if (pipelineName !== 'new') { + const msg = 'Pipeline name does not match the URL'; + Logger.error(msg); + throw new HttpException(msg, HttpStatus.BAD_REQUEST); + } + //TODO: Migration -> this is a mock user const user: IUser = { id: 1, diff --git a/server-refactored-v3/src/repo/repo.controller.ts b/server-refactored-v3/src/repo/repo.controller.ts index 4983dba6..4d096ea9 100644 --- a/server-refactored-v3/src/repo/repo.controller.ts +++ b/server-refactored-v3/src/repo/repo.controller.ts @@ -49,26 +49,26 @@ export class RepoController { return this.repoService.listReferences(provider, gitrepob64); } - @Post('/:repoprovider/connect') + @Post('/:provider/connect') @ApiOperation({ summary: 'Connect a repository' }) async connectRepo( - @Param('repoprovider') repoprovider: string, + @Param('provider') provider: string, @Body() body: any, ) { - return this.repoService.connectRepo(repoprovider, body.gitrepo); + return this.repoService.connectRepo(provider, body.gitrepo); } @ApiOperation({ summary: 'Disconnect a repository' }) - @Post('/:repoprovider/disconnect') + @Post('/:provider/disconnect') async disconnectRepo( - @Param('repoprovider') repoprovider: string, + @Param('provider') provider: string, @Body() body: any, ) { - return this.repoService.disconnectRepo(repoprovider, body.gitrepo); + return this.repoService.disconnectRepo(provider, body.gitrepo); } @ApiOperation({ summary: 'Webhooks endpoint for repository providers' }) - @Post('/repo/webhooks/:repoprovider') + @Post('/repo/webhooks/:provider') async repositoryWebhook( @Body() body: any, ) { From 65a9117e5d3c82cf70a232144e3384d6222a6201 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 13 Feb 2025 02:05:15 +0100 Subject: [PATCH 39/65] migrate app update --- client/src/components/apps/form.vue | 6 ++- server-refactored-v3/src/apps/app/app.ts | 1 + .../src/apps/apps.controller.ts | 28 ++++++++++++- server-refactored-v3/src/apps/apps.service.ts | 39 +++++++++++++++++++ server/src/kubero.ts | 1 + 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/client/src/components/apps/form.vue b/client/src/components/apps/form.vue index eebf15a1..94ae2784 100644 --- a/client/src/components/apps/form.vue +++ b/client/src/components/apps/form.vue @@ -2127,7 +2127,7 @@ export default defineComponent({ loadApp() { if (this.app !== 'new') { axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`).then(response => { - this.resourceVersion = response.data.resourceVersion; + this.resourceVersion = response.data.metadata.resourceVersion; // Open Panel if there is some data to show if (response.data.spec.envVars.length > 0) { @@ -2274,6 +2274,8 @@ export default defineComponent({ } let postdata = { + pipeline: this.pipeline, + phase: this.phase, resourceVersion: this.resourceVersion, buildpack: this.buildpack, name: this.name, @@ -2337,7 +2339,7 @@ export default defineComponent({ postdata.image.run.securityContext.runAsGroup = parseInt(postdata.image.run.securityContext.runAsGroup); } - axios.put(`/api/apps/${this.pipeline}/${this.phase}/${this.app}`, postdata + axios.put(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/${this.resourceVersion}`, postdata // eslint-disable-next-line no-unused-vars ).then(response => { this.$router.push(`/pipeline/${this.pipeline}/apps`); diff --git a/server-refactored-v3/src/apps/app/app.ts b/server-refactored-v3/src/apps/app/app.ts index 8d179ffb..996c5fa9 100644 --- a/server-refactored-v3/src/apps/app/app.ts +++ b/server-refactored-v3/src/apps/app/app.ts @@ -16,6 +16,7 @@ export class KubectlApp implements IKubectlApp{ kind: string; metadata: IKubectlMetadata; spec: App; + status: { conditions: [Array: Object]; deployedRelease?: { name: string; manifest: string; }; }; constructor(app: App) { this.apiVersion = "application.kubero.dev/v1alpha1"; diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index 77866aff..c8ecc93a 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Logger, Param, Post, Res } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Logger, Param, Post, Put, Res } from '@nestjs/common'; import { Response } from 'express'; import { AppsService } from './apps.service'; import { IUser } from '../auth/auth.interface'; @@ -56,6 +56,32 @@ export class AppsController { return this.appsService.createApp(app, user); } + @ApiOperation({ summary: 'Update an app' }) + @Put('/:pipeline/:phase/:app/:resourceVersion') + async updateApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') appName: string, + @Param('resourceVersion') resourceVersion: string, + @Body() app: any, + ) { + + if (appName !== app.name) { + const msg = 'App name does not match the URL '+appName+' != '+app.name; + Logger.error(msg); + throw new HttpException(msg, HttpStatus.BAD_REQUEST); + } + + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890' + }; + return this.appsService.updateApp(app, resourceVersion, user); + } + @ApiOperation({ summary: 'Delete an app' }) @Delete('/:pipeline/:phase/:app') async deleteApp( diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index 478ecacf..d66af5e4 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -335,4 +335,43 @@ export class AppsService { this.NotificationsService.send(m); } } + + + // update an app in a pipeline and phase + public async updateApp(app: App, resourceVersion: string, user: IUser) { + this.logger.debug('update App: '+app.name+' in '+ app.pipeline+' phase: '+app.phase); + //await this.pipelinesService.setContext(app.pipeline, app.phase); + + if ( process.env.KUBERO_READONLY == 'true'){ + console.log('KUBERO_READONLY is set to true, not updating app ' + app.name); + return; + } + + const contextName = await this.pipelinesService.getContext(app.pipeline, app.phase); + + if (app.deploymentstrategy == 'git' && (app.buildstrategy == 'dockerfile' || app.buildstrategy == 'nixpacks' || app.buildstrategy == 'buildpacks')){ + this.triggerImageBuild(app.pipeline, app.phase, app.name); + } + + if (contextName) { + await this.kubectl.updateApp(app, resourceVersion, contextName); + // IMPORTANT TODO : Update this.appStateList !! + + const m = { + 'name': 'updateApp', + 'user': user.username, + 'resource': 'app', + 'action': 'update', + 'severity': 'normal', + 'message': 'Updated app: '+app.name+' in '+ app.pipeline+' phase: '+app.phase, + 'pipelineName':app.pipeline, + 'phaseName': app.phase, + 'appName': app.name, + 'data': { + 'app': app + } + } as INotification; + this.NotificationsService.send(m); + } + } } diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 10453779..4d238d50 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -457,6 +457,7 @@ export class Kubero { } } + //Migrated to apps // get a app in a pipeline and phase public async getApp(pipelineName: string, phaseName: string, appName: string) { debug.debug('get App: '+appName+' in '+ pipelineName+' phase: '+phaseName); From 3af52d5a2dd1fbdf77bade790454c269e2a8118a Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 13 Feb 2025 03:31:31 +0100 Subject: [PATCH 40/65] migrate vulnerability scans --- .../src/security/security.controller.ts | 10 ++++ .../src/security/security.service.ts | 49 +++++++++++++++++-- server/src/kubero.ts | 1 + 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/server-refactored-v3/src/security/security.controller.ts b/server-refactored-v3/src/security/security.controller.ts index 83f0139d..70a38c1f 100644 --- a/server-refactored-v3/src/security/security.controller.ts +++ b/server-refactored-v3/src/security/security.controller.ts @@ -8,6 +8,16 @@ export class SecurityController { private securityService: SecurityService, ) {} + @ApiOperation({ summary: 'Trigger a scan for a specific app' }) + @Get(':pipeline/:phase/:app/scan') + async triggerScan( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + ) { + return this.securityService.startScan(pipeline, phase, app); + } + @ApiOperation({ summary: 'Get the scan result for a specific app' }) @Get(':pipeline/:phase/:app/scan/result') async getScanResult( diff --git a/server-refactored-v3/src/security/security.service.ts b/server-refactored-v3/src/security/security.service.ts index a6fc1730..56ef3432 100644 --- a/server-refactored-v3/src/security/security.service.ts +++ b/server-refactored-v3/src/security/security.service.ts @@ -6,7 +6,7 @@ import { AppsService } from '../apps/apps.service'; @Injectable() export class SecurityService { - private Logger = new Logger(SecurityService.name); + private logger = new Logger(SecurityService.name); constructor( private kubectl: KubernetesService, @@ -88,8 +88,8 @@ export class SecurityService { } if (!logs || !logs.Results) { - this.Logger.error(logs); - this.Logger.error('no logs found or not able to parse results'); + this.logger.error(logs); + this.logger.error('no logs found or not able to parse results'); return summary; } @@ -122,4 +122,47 @@ export class SecurityService { return summary; } + + public async startScan(pipeline: string, phase: string, appName: string) { + const contextName = await this.pipelinesService.getContext(pipeline, phase); + const namespace = pipeline+'-'+phase; + + + const appresult = await this.appsService.getApp(pipeline, phase, appName) + + const app = appresult as IKubectlApp; + + + if (app?.spec?.deploymentstrategy === 'git' && app?.spec?.buildstrategy === 'plain') { + //if (app?.spec?.deploymentstrategy === 'git') { + + if (app?.spec.gitrepo?.clone_url) { + if (contextName) { + this.kubectl.setCurrentContext(contextName); + this.kubectl.createScanRepoJob(namespace, appName, app.spec.gitrepo.clone_url, app.spec.branch); + } + } else { + this.logger.debug('no git repo found to run scan'); + } + } else if (app?.spec?.deploymentstrategy === 'git' && app?.spec?.buildstrategy != 'plain') { + if (contextName) { + this.kubectl.setCurrentContext(contextName); + this.kubectl.createScanImageJob(namespace, appName, app.spec.image.repository, app.spec.image.tag, true); + } + } else { + if (contextName) { + this.kubectl.setCurrentContext(contextName); + this.kubectl.createScanImageJob(namespace, appName, app.spec.image.repository, app.spec.image.tag, false); + } + } + + return { + status: 'ok', + message: 'scan started', + deploymentstrategy: app?.spec?.deploymentstrategy, + pipeline: pipeline, + phase: phase, + app: appName + }; + } } diff --git a/server/src/kubero.ts b/server/src/kubero.ts index 4d238d50..d6036af1 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -1287,6 +1287,7 @@ export class Kubero { return this.kubectl.getStorageglasses(); } + //Migrated to security public async startScan(pipeline: string, phase: string, appName: string) { const contextName = this.getContext(pipeline, phase); const namespace = pipeline+'-'+phase; From 381a332dfce9e7f50018f481fa2f391bb321ec49 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 13 Feb 2025 03:59:39 +0100 Subject: [PATCH 41/65] Migration: linting --- .../src/addons/addons.controller.ts | 5 +- .../src/addons/addons.interface.ts | 70 +- .../src/addons/addons.module.ts | 3 +- .../src/addons/addons.service.ts | 86 +- .../src/addons/plugins/clickhouse.ts | 340 +-- .../src/addons/plugins/cloudflare.ts | 225 +- .../src/addons/plugins/cockroachDB.ts | 211 +- .../src/addons/plugins/kuberoCouchDB.ts | 192 +- .../src/addons/plugins/kuberoElasticsearch.ts | 192 +- .../src/addons/plugins/kuberoKafka.ts | 96 +- .../src/addons/plugins/kuberoMail.ts | 113 +- .../src/addons/plugins/kuberoMemcached.ts | 226 +- .../src/addons/plugins/kuberoMongoDB.ts | 224 +- .../src/addons/plugins/kuberoMysql.ts | 176 +- .../src/addons/plugins/kuberoPostgresql.ts | 161 +- .../src/addons/plugins/kuberoRabbitMQ.ts | 176 +- .../src/addons/plugins/kuberoRedis.ts | 144 +- .../src/addons/plugins/minio.ts | 378 +-- .../src/addons/plugins/mongoDB.ts | 155 +- .../src/addons/plugins/mongoDBCluster.ts | 97 +- .../src/addons/plugins/plugin.interface.ts | 36 +- .../src/addons/plugins/plugin.ts | 310 +- .../src/addons/plugins/postgresCluster.ts | 353 +-- .../src/addons/plugins/redis.ts | 149 +- .../src/addons/plugins/redisCluster.ts | 164 +- .../src/app.controller.spec.ts | 1 - server-refactored-v3/src/app.controller.ts | 12 +- server-refactored-v3/src/app.module.ts | 1 - server-refactored-v3/src/app.service.ts | 3 +- server-refactored-v3/src/apps/app/app.ts | 459 +-- .../src/apps/apps.controller.ts | 37 +- .../src/apps/apps.interface.ts | 250 +- .../src/apps/apps.service.spec.ts | 21 +- server-refactored-v3/src/apps/apps.service.ts | 718 +++-- .../src/audit/audit.controller.ts | 23 +- .../src/audit/audit.interface.ts | 36 +- .../src/audit/audit.service.ts | 411 +-- .../src/auth/auth.controller.ts | 24 +- .../src/auth/auth.interface.ts | 12 +- server-refactored-v3/src/auth/auth.module.ts | 4 +- server-refactored-v3/src/auth/auth.service.ts | 39 +- .../src/auth/local.strategy.ts | 2 +- server-refactored-v3/src/core/core.module.ts | 2 +- .../src/deployments/deployments.controller.ts | 7 +- .../src/deployments/deployments.interface.ts | 204 +- .../src/deployments/deployments.module.ts | 2 +- .../src/deployments/deployments.service.ts | 365 ++- server-refactored-v3/src/dto/ok.dto.ts | 5 +- .../src/events/events.gateway.ts | 22 +- .../src/events/events.module.ts | 4 +- .../src/kubernetes/dto/kubernetes.dto.ts | 120 +- .../src/kubernetes/kubernetes.controller.ts | 36 +- .../src/kubernetes/kubernetes.interface.ts | 41 +- .../src/kubernetes/kubernetes.service.ts | 2520 +++++++++-------- server-refactored-v3/src/logger/logger.ts | 10 +- .../src/logs/logs.controller.ts | 10 +- .../src/logs/logs.interface.ts | 22 +- server-refactored-v3/src/logs/logs.module.ts | 2 +- server-refactored-v3/src/logs/logs.service.ts | 272 +- server-refactored-v3/src/main.ts | 38 +- .../src/metrics/metrics.controller.ts | 4 +- .../src/metrics/metrics.interface.ts | 42 +- .../src/metrics/metrics.module.ts | 2 +- .../src/metrics/metrics.service.ts | 641 +++-- .../notifications/notifications.interface.ts | 44 +- .../src/notifications/notifications.module.ts | 7 +- .../notifications/notifications.service.ts | 275 +- .../src/pipelines/dto/getPipeline.dto.ts | 203 +- .../src/pipelines/dto/replacePipeline.dto.ts | 26 +- .../src/pipelines/pipeline/pipeline.spec.ts | 31 +- .../src/pipelines/pipeline/pipeline.ts | 83 +- .../src/pipelines/pipelines.controller.ts | 96 +- .../src/pipelines/pipelines.interface.ts | 34 +- .../src/pipelines/pipelines.service.ts | 277 +- .../src/repo/git/bitbucket.ts | 707 ++--- server-refactored-v3/src/repo/git/gitea.ts | 666 ++--- server-refactored-v3/src/repo/git/github.ts | 765 ++--- server-refactored-v3/src/repo/git/gitlab.ts | 694 ++--- server-refactored-v3/src/repo/git/gogs.ts | 613 ++-- .../src/repo/git/repo.test.ts | 42 +- server-refactored-v3/src/repo/git/repo.ts | 287 +- server-refactored-v3/src/repo/git/types.ts | 132 +- .../src/repo/repo.controller.ts | 24 +- .../src/repo/repo.interface.ts | 2 +- server-refactored-v3/src/repo/repo.module.ts | 2 +- server-refactored-v3/src/repo/repo.service.ts | 341 +-- .../src/security/security.controller.ts | 12 +- .../src/security/security.service.ts | 227 +- .../src/settings/buildpack/buildpack.ts | 148 +- .../settings/kubero-config/kubero-config.ts | 83 +- .../src/settings/podsize/podsize.ts | 62 +- .../src/settings/settings.controller.ts | 100 +- .../src/settings/settings.interface.ts | 245 +- .../src/settings/settings.module.ts | 2 +- .../src/settings/settings.service.ts | 564 ++-- .../src/templates/template.ts | 144 +- .../src/templates/templates.interface.ts | 63 +- .../src/templates/templates/templates.ts | 138 +- .../src/users/users.module.ts | 2 +- .../src/users/users.service.ts | 4 +- 100 files changed, 9377 insertions(+), 8474 deletions(-) diff --git a/server-refactored-v3/src/addons/addons.controller.ts b/server-refactored-v3/src/addons/addons.controller.ts index caf792c1..70948f38 100644 --- a/server-refactored-v3/src/addons/addons.controller.ts +++ b/server-refactored-v3/src/addons/addons.controller.ts @@ -4,10 +4,7 @@ import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/addons', version: '1' }) export class AddonsController { - constructor( - private readonly addonsService: AddonsService - ) {} - + constructor(private readonly addonsService: AddonsService) {} @ApiOperation({ summary: 'Get a list of all addons' }) @Get('/') diff --git a/server-refactored-v3/src/addons/addons.interface.ts b/server-refactored-v3/src/addons/addons.interface.ts index 8a40360f..92ce65d0 100644 --- a/server-refactored-v3/src/addons/addons.interface.ts +++ b/server-refactored-v3/src/addons/addons.interface.ts @@ -1,20 +1,22 @@ - -import { KubernetesListObject, KubernetesObject } from '@kubernetes/client-node' +import { + KubernetesListObject, + KubernetesObject, +} from '@kubernetes/client-node'; export interface IAddon { - id: string - operator: string, - enabled: boolean, - name: string, - CRDkind: string, - icon: string, - displayName: string, - version: string + id: string; + operator: string; + enabled: boolean; + name: string; + CRDkind: string; + icon: string; + displayName: string; + version: string; plural: string; - description?: string, - install: string, - formfields: {[key: string]: IAddonFormFields}, - crd: KubernetesObject + description?: string; + install: string; + formfields: { [key: string]: IAddonFormFields }; + crd: KubernetesObject; } interface IAddonMinimal { @@ -28,31 +30,31 @@ interface IAddonMinimal { } interface IAddonFormFields { - type: 'text' | 'number' |'switch', - label: string, - name: string, - required: boolean, - default: string | number | boolean, - description?: string, + type: 'text' | 'number' | 'switch'; + label: string; + name: string; + required: boolean; + default: string | number | boolean; + description?: string; //value?: string | number | boolean, } export interface IAddon { - id: string - operator: string, - enabled: boolean, - name: string, - CRDkind: string, - icon: string, - displayName: string, - version: string + id: string; + operator: string; + enabled: boolean; + name: string; + CRDkind: string; + icon: string; + displayName: string; + version: string; plural: string; - description?: string, - install: string, - formfields: {[key: string]: IAddonFormFields}, - crd: KubernetesObject + description?: string; + install: string; + formfields: { [key: string]: IAddonFormFields }; + crd: KubernetesObject; } interface IUniqueAddons { - [key: string]: IAddon -} \ No newline at end of file + [key: string]: IAddon; +} diff --git a/server-refactored-v3/src/addons/addons.module.ts b/server-refactored-v3/src/addons/addons.module.ts index 485aa530..8cce7a48 100644 --- a/server-refactored-v3/src/addons/addons.module.ts +++ b/server-refactored-v3/src/addons/addons.module.ts @@ -5,5 +5,4 @@ import { AddonsService } from './addons.service'; controllers: [AddonsController], providers: [AddonsService], }) -export class AddonsModule { -} +export class AddonsModule {} diff --git a/server-refactored-v3/src/addons/addons.service.ts b/server-refactored-v3/src/addons/addons.service.ts index ca74c4f9..b868c62c 100644 --- a/server-refactored-v3/src/addons/addons.service.ts +++ b/server-refactored-v3/src/addons/addons.service.ts @@ -24,81 +24,77 @@ import { KubernetesService } from '../kubernetes/kubernetes.service'; @Injectable() export class AddonsService { private operatorsAvailable: string[] = []; - public addonsList: IPlugin[] = [] // List or possibly installed operators + public addonsList: IPlugin[] = []; // List or possibly installed operators private CRDList: any; //List of installed CRDs from kubectl constructor(private kubectl: KubernetesService) { - this.loadOperators() + this.loadOperators(); } public async loadOperators(): Promise { - // Load all Custom Resource Definitions to get the list of installed operators - this.CRDList = await this.kubectl.getCustomresources() - - const kuberoMysql = new KuberoMysql(this.CRDList) - this.addonsList.push(kuberoMysql) + this.CRDList = await this.kubectl.getCustomresources(); - const kuberoRedis = new KuberoRedis(this.CRDList) - this.addonsList.push(kuberoRedis) + const kuberoMysql = new KuberoMysql(this.CRDList); + this.addonsList.push(kuberoMysql); - const kuberoPostgresql = new KuberoPostgresql(this.CRDList) - this.addonsList.push(kuberoPostgresql) + const kuberoRedis = new KuberoRedis(this.CRDList); + this.addonsList.push(kuberoRedis); - const kuberoMongoDB = new KuberoMongoDB(this.CRDList) - this.addonsList.push(kuberoMongoDB) + const kuberoPostgresql = new KuberoPostgresql(this.CRDList); + this.addonsList.push(kuberoPostgresql); - const kuberoMemcached = new KuberoMemcached(this.CRDList) - this.addonsList.push(kuberoMemcached) + const kuberoMongoDB = new KuberoMongoDB(this.CRDList); + this.addonsList.push(kuberoMongoDB); - const kuberoElasticsearch = new KuberoElasticsearch(this.CRDList) - this.addonsList.push(kuberoElasticsearch) + const kuberoMemcached = new KuberoMemcached(this.CRDList); + this.addonsList.push(kuberoMemcached); - const kuberoCouchDB = new KuberoCouchDB(this.CRDList) - this.addonsList.push(kuberoCouchDB) + const kuberoElasticsearch = new KuberoElasticsearch(this.CRDList); + this.addonsList.push(kuberoElasticsearch); - const kuberoKafka = new KuberoKafka(this.CRDList) - this.addonsList.push(kuberoKafka) + const kuberoCouchDB = new KuberoCouchDB(this.CRDList); + this.addonsList.push(kuberoCouchDB); - const kuberoMail = new KuberoMail(this.CRDList) - this.addonsList.push(kuberoMail) + const kuberoKafka = new KuberoKafka(this.CRDList); + this.addonsList.push(kuberoKafka); - const kuberoRabbitMQ = new KuberoRabbitMQ(this.CRDList) - this.addonsList.push(kuberoRabbitMQ) + const kuberoMail = new KuberoMail(this.CRDList); + this.addonsList.push(kuberoMail); - const tunnel = new Tunnel(this.CRDList) - this.addonsList.push(tunnel) + const kuberoRabbitMQ = new KuberoRabbitMQ(this.CRDList); + this.addonsList.push(kuberoRabbitMQ); - const postgresCluster = new PostgresCluster(this.CRDList) - this.addonsList.push(postgresCluster) + const tunnel = new Tunnel(this.CRDList); + this.addonsList.push(tunnel); - const redisCluster = new RedisCluster(this.CRDList) - this.addonsList.push(redisCluster) + const postgresCluster = new PostgresCluster(this.CRDList); + this.addonsList.push(postgresCluster); - const redis = new Redis(this.CRDList) - this.addonsList.push(redis) + const redisCluster = new RedisCluster(this.CRDList); + this.addonsList.push(redisCluster); - const mongoDB = new MongoDB(this.CRDList) - this.addonsList.push(mongoDB) + const redis = new Redis(this.CRDList); + this.addonsList.push(redis); - const cockroachdb = new Cockroachdb(this.CRDList) - this.addonsList.push(cockroachdb) + const mongoDB = new MongoDB(this.CRDList); + this.addonsList.push(mongoDB); - const minio = new Tenant(this.CRDList) - this.addonsList.push(minio) + const cockroachdb = new Cockroachdb(this.CRDList); + this.addonsList.push(cockroachdb); - const clickhouse = new ClickHouseInstallation(this.CRDList) - this.addonsList.push(clickhouse) + const minio = new Tenant(this.CRDList); + this.addonsList.push(minio); + const clickhouse = new ClickHouseInstallation(this.CRDList); + this.addonsList.push(clickhouse); } public async getAddonsList(): Promise { - return this.addonsList + return this.addonsList; } public getOperatorsList(): string[] { - return this.operatorsAvailable + return this.operatorsAvailable; } - - } diff --git a/server-refactored-v3/src/addons/plugins/clickhouse.ts b/server-refactored-v3/src/addons/plugins/clickhouse.ts index f12be36e..41cb1dd7 100644 --- a/server-refactored-v3/src/addons/plugins/clickhouse.ts +++ b/server-refactored-v3/src/addons/plugins/clickhouse.ts @@ -1,181 +1,181 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class ClickHouseInstallation extends Plugin implements IPlugin { - public id: string = 'clickhouse-operator';//same as operator name - public displayName = 'ClickHouse Cluster' - public icon = '/img/addons/clickhouse.svg' - public install = 'curl -s https://raw.githubusercontent.com/Altinity/clickhouse-operator/master/deploy/operator-web-installer/clickhouse-operator-install.sh | OPERATOR_NAMESPACE=clickhouse-operator-system bash' - public url = 'https://github.com/Altinity/clickhouse-operator/' - public description: string = 'ClickHouse is an open source column-oriented database management system capable of real time generation of analytical data reports. Check ClickHouse documentation for more complete details.' - public links = [ - { - name: 'Altinity', url: 'https://altinity.com/', - }, - { - name: 'Operator homepage', url: 'https://www.altinity.com/kubernetes-operator' - }, - { - name: 'Documentation', url: 'https://github.com/Altinity/clickhouse-operator/tree/master/docs' - } - ] - public maintainers = [ - { - name: 'Altinity', - email: 'support@altinity.com', - url: 'https://altinity.com', - github: 'altinity' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/clickhouse' - public beta: boolean = true; + public id: string = 'clickhouse-operator'; //same as operator name + public displayName = 'ClickHouse Cluster'; + public icon = '/img/addons/clickhouse.svg'; + public install = + 'curl -s https://raw.githubusercontent.com/Altinity/clickhouse-operator/master/deploy/operator-web-installer/clickhouse-operator-install.sh | OPERATOR_NAMESPACE=clickhouse-operator-system bash'; + public url = 'https://github.com/Altinity/clickhouse-operator/'; + public description: string = + 'ClickHouse is an open source column-oriented database management system capable of real time generation of analytical data reports. Check ClickHouse documentation for more complete details.'; + public links = [ + { + name: 'Altinity', + url: 'https://altinity.com/', + }, + { + name: 'Operator homepage', + url: 'https://www.altinity.com/kubernetes-operator', + }, + { + name: 'Documentation', + url: 'https://github.com/Altinity/clickhouse-operator/tree/master/docs', + }, + ]; + public maintainers = [ + { + name: 'Altinity', + email: 'support@altinity.com', + url: 'https://altinity.com', + github: 'altinity', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/clickhouse'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'ClickHouseInstallation.metadata.name':{ - type: 'text', - label: 'Name *', - name: 'metadata.name', - required: true, - default: 'clickhouse', - description: 'The name of the Clickhouse instance' - }, - 'ClickHouseInstallation.spec.configuration.clusters[0].layout.shardsCount':{ - type: 'number', - label: 'Shards Count *', - name: 'spec.configuration.clusters[0].layout.shardsCount', - default: 1, - required: true, - description: 'Number of shards' - }, - 'ClickHouseInstallation.spec.configuration.clusters[0].layout.replicasCount':{ - type: 'number', - label: 'Replicas Count *', - name: 'spec.configuration.clusters[0].layout.replicasCount', - default: 1, - required: true, - description: 'Number of replicas' - }, - "ClickHouseInstallation.spec.configuration.users['admin/password']":{ - type: 'text', - label: 'Admin Password *', - name: "ClickHouseInstallation.spec.configuration.users['admin/password']", - default: 'ChangeMe', - required: true, - description: 'Password for user "user"' - }, - "ClickHouseInstallation.spec.configuration.users['admin/networks/ip'][0]":{ - type: 'text', - label: 'Admin Access Network *', - name: "ClickHouseInstallation.spec.configuration.users['admin/networks/ip'][0]", - default: '0.0.0.0/0', - required: true, - description: 'Allowed Network access for "admin"' - }, - "ClickHouseInstallation.spec.configuration.users['user/password']":{ - type: 'text', - label: 'User Password *', - name: "ClickHouseInstallation.spec.configuration.users['user/password']", - default: 'ChangeMe', - required: true, - description: 'Password for user "user"' - }, - "ClickHouseInstallation.spec.configuration.users['user/networks/ip'][0]":{ - type: 'text', - label: 'User Access Network *', - name: "ClickHouseInstallation.spec.configuration.users['user/networks/ip'][0]", - default: '0.0.0.0/0', - required: true, - description: 'Allowed Network access for "user"' - }, - 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage':{ - type: 'text', - label: 'Data Storage Size*', - name: 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage', - default: '1Gi', - required: true, - description: 'Size of the data storage' - }, - 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[1].spec.resources.requests.storage':{ - type: 'text', - label: 'Log Storage Size*', - name: 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage', - default: '1Gi', - required: true, - description: 'Size of the log storage' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'ClickHouseInstallation.metadata.name': { + type: 'text', + label: 'Name *', + name: 'metadata.name', + required: true, + default: 'clickhouse', + description: 'The name of the Clickhouse instance', + }, + 'ClickHouseInstallation.spec.configuration.clusters[0].layout.shardsCount': + { + type: 'number', + label: 'Shards Count *', + name: 'spec.configuration.clusters[0].layout.shardsCount', + default: 1, + required: true, + description: 'Number of shards', + }, + 'ClickHouseInstallation.spec.configuration.clusters[0].layout.replicasCount': + { + type: 'number', + label: 'Replicas Count *', + name: 'spec.configuration.clusters[0].layout.replicasCount', + default: 1, + required: true, + description: 'Number of replicas', + }, + "ClickHouseInstallation.spec.configuration.users['admin/password']": { + type: 'text', + label: 'Admin Password *', + name: "ClickHouseInstallation.spec.configuration.users['admin/password']", + default: 'ChangeMe', + required: true, + description: 'Password for user "user"', + }, + "ClickHouseInstallation.spec.configuration.users['admin/networks/ip'][0]": { + type: 'text', + label: 'Admin Access Network *', + name: "ClickHouseInstallation.spec.configuration.users['admin/networks/ip'][0]", + default: '0.0.0.0/0', + required: true, + description: 'Allowed Network access for "admin"', + }, + "ClickHouseInstallation.spec.configuration.users['user/password']": { + type: 'text', + label: 'User Password *', + name: "ClickHouseInstallation.spec.configuration.users['user/password']", + default: 'ChangeMe', + required: true, + description: 'Password for user "user"', + }, + "ClickHouseInstallation.spec.configuration.users['user/networks/ip'][0]": { + type: 'text', + label: 'User Access Network *', + name: "ClickHouseInstallation.spec.configuration.users['user/networks/ip'][0]", + default: '0.0.0.0/0', + required: true, + description: 'Allowed Network access for "user"', + }, + 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage': + { + type: 'text', + label: 'Data Storage Size*', + name: 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage', + default: '1Gi', + required: true, + description: 'Size of the data storage', + }, + 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[1].spec.resources.requests.storage': + { + type: 'text', + label: 'Log Storage Size*', + name: 'ClickHouseInstallation.spec.templates.volumeClaimTemplates[0].spec.resources.requests.storage', + default: '1Gi', + required: true, + description: 'Size of the log storage', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } - public resourceDefinitions: any = { - ClickHouseInstallation: { - apiVersion: "clickhouse.altinity.com/v1", - kind: "ClickHouseInstallation", - metadata: { - name: "example" + public resourceDefinitions: any = { + ClickHouseInstallation: { + apiVersion: 'clickhouse.altinity.com/v1', + kind: 'ClickHouseInstallation', + metadata: { + name: 'example', + }, + spec: { + configuration: { + users: { + 'user/password': 'user_password', + 'user/networks/ip': ['0.0.0.0/0'], + 'admin/password': 'admin_password', + 'admin/networks/ip': ['0.0.0.0/0'], + }, + clusters: [ + { + name: 'example', + layout: { + shardsCount: 1, + replicasCount: 2, + }, }, - spec: { - configuration: { - users: { - 'user/password': "user_password", - 'user/networks/ip': [ - "0.0.0.0/0" - ], - 'admin/password': "admin_password", - 'admin/networks/ip': [ - "0.0.0.0/0" - ] - }, - clusters: [ - { - name: "example", - layout: { - shardsCount: 1, - replicasCount: 2 - } - } - ] + ], + }, + templates: { + volumeClaimTemplates: [ + { + name: 'data-volume-template', + spec: { + accessModes: ['ReadWriteOnce'], + resources: { + requests: { + storage: '1Gi', + }, }, - templates: { - volumeClaimTemplates: [ - { - name: "data-volume-template", - spec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "1Gi" - } - } - } - }, - { - name: "log-volume-template", - spec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "100Mi" - } - } - } - } - ] - } - } - } - } - + }, + }, + { + name: 'log-volume-template', + spec: { + accessModes: ['ReadWriteOnce'], + resources: { + requests: { + storage: '100Mi', + }, + }, + }, + }, + ], + }, + }, + }, + }; } - diff --git a/server-refactored-v3/src/addons/plugins/cloudflare.ts b/server-refactored-v3/src/addons/plugins/cloudflare.ts index bf044616..72b5fd8a 100644 --- a/server-refactored-v3/src/addons/plugins/cloudflare.ts +++ b/server-refactored-v3/src/addons/plugins/cloudflare.ts @@ -1,69 +1,74 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class Tunnel extends Plugin implements IPlugin { - public id: string = 'cloudflare-operator';//same as operator name - public displayName = 'Cloudflare Tunnel' - public icon = '/img/addons/cloudflare.svg' - public install: string = 'kubectl apply -k https://github.com/adyanth/cloudflare-operator/config/default' - public url = 'https://github.com/adyanth/cloudflare-operator' - public description: string = 'Cloudflared establishes outbound connections (tunnels) between your resources and Cloudflare\'s global network. Tunnels are persistent objects that route traffic to DNS records. Within the same tunnel, you can run as many cloudflared processes (connectors) as needed.' - public links = [ - { - name: 'Getting started', url: 'https://github.com/adyanth/cloudflare-operator/blob/main/docs/getting-started.md', - }, - { - name: 'Cloudflare Tunnel', url: 'https://developers.cloudflare.com/cloudflare-one/connections/connect-apps' - }, - { - name: 'Blog Post', url: 'https://adyanth.site/posts/migration-compose-k8s/cloudflare-tunnel-operator-architecture/' - } - ] - public maintainers = [ - { - name: 'Adyanth Hosavalike', - email: 'me@adyanth.dev', - url: 'https://adyanth.site', - github: 'adyanth' - } - ] - public artifact_url = 'https://www.httpbin.org/status/404' // Not available on ArtifactHub - public beta: boolean = true; + public id: string = 'cloudflare-operator'; //same as operator name + public displayName = 'Cloudflare Tunnel'; + public icon = '/img/addons/cloudflare.svg'; + public install: string = + 'kubectl apply -k https://github.com/adyanth/cloudflare-operator/config/default'; + public url = 'https://github.com/adyanth/cloudflare-operator'; + public description: string = + "Cloudflared establishes outbound connections (tunnels) between your resources and Cloudflare's global network. Tunnels are persistent objects that route traffic to DNS records. Within the same tunnel, you can run as many cloudflared processes (connectors) as needed."; + public links = [ + { + name: 'Getting started', + url: 'https://github.com/adyanth/cloudflare-operator/blob/main/docs/getting-started.md', + }, + { + name: 'Cloudflare Tunnel', + url: 'https://developers.cloudflare.com/cloudflare-one/connections/connect-apps', + }, + { + name: 'Blog Post', + url: 'https://adyanth.site/posts/migration-compose-k8s/cloudflare-tunnel-operator-architecture/', + }, + ]; + public maintainers = [ + { + name: 'Adyanth Hosavalike', + email: 'me@adyanth.dev', + url: 'https://adyanth.site', + github: 'adyanth', + }, + ]; + public artifact_url = 'https://www.httpbin.org/status/404'; // Not available on ArtifactHub + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'Tunnel.metadata.name':{ - type: 'text', - label: 'Name', - name: 'metadata.name', - required: true, - default: 'cloudflare-tunnel', - description: 'The name of the Cloudflare Tunnel' - }, - 'Tunnel.spec.cloudflare.domain':{ - type: 'text', - label: 'Domain*', - name: 'spec.memcached.domain', - default: '', - required: true, - description: 'Memcached admin user' - }, - 'Tunnel.spec.cloudflare.email':{ - type: 'text', - label: 'E-mail*', - name: 'spec.cloudflare.email', - default: '', - required: true, - description: 'Email address associated with the Cloudflare account' - }, - 'Tunnel.spec.cloudflare.accountName':{ - type: 'text', - label: 'Account Name*', - name: 'spec.cloudflare.accountName', - default: '', - required: true, - description: 'Cloudflare Account Name' - }, - /* Fallback to Account Name + public formfields: { [key: string]: IPluginFormFields } = { + 'Tunnel.metadata.name': { + type: 'text', + label: 'Name', + name: 'metadata.name', + required: true, + default: 'cloudflare-tunnel', + description: 'The name of the Cloudflare Tunnel', + }, + 'Tunnel.spec.cloudflare.domain': { + type: 'text', + label: 'Domain*', + name: 'spec.memcached.domain', + default: '', + required: true, + description: 'Memcached admin user', + }, + 'Tunnel.spec.cloudflare.email': { + type: 'text', + label: 'E-mail*', + name: 'spec.cloudflare.email', + default: '', + required: true, + description: 'Email address associated with the Cloudflare account', + }, + 'Tunnel.spec.cloudflare.accountName': { + type: 'text', + label: 'Account Name*', + name: 'spec.cloudflare.accountName', + default: '', + required: true, + description: 'Cloudflare Account Name', + }, + /* Fallback to Account Name 'Tunnel.spec.cloudflare.accountId':{ type: 'text', label: 'Account ID', @@ -73,57 +78,55 @@ export class Tunnel extends Plugin implements IPlugin { description: 'Cloudflare Account ID' }, */ - 'Tunnel.spec.cloudflare.CLOUDFLARE_API_TOKEN':{ - type: 'text', - label: 'API Token*', - name: 'spec.cloudflare.CLOUDFLARE_API_TOKEN', - default: '', - required: true, - description: 'Cloudflare API Token' - }, - 'Tunnel.spec.cloudflare.CLOUDFLARE_API_KEY':{ - type: 'text', - label: 'API Key*', - name: 'spec.cloudflare.CLOUDFLARE_API_KEY', - default: '', - required: true, - description: 'Cloudflare API Key' - }, - }; - - public env: any[] = [] + 'Tunnel.spec.cloudflare.CLOUDFLARE_API_TOKEN': { + type: 'text', + label: 'API Token*', + name: 'spec.cloudflare.CLOUDFLARE_API_TOKEN', + default: '', + required: true, + description: 'Cloudflare API Token', + }, + 'Tunnel.spec.cloudflare.CLOUDFLARE_API_KEY': { + type: 'text', + label: 'API Key*', + name: 'spec.cloudflare.CLOUDFLARE_API_KEY', + default: '', + required: true, + description: 'Cloudflare API Key', + }, + }; - protected additionalResourceDefinitions: Object = {} + public env: any[] = []; - public resourceDefinitions: any = { - Tunnel: { - apiVersion: "networking.cfargotunnel.com/v1alpha1", - kind: "Tunnel", - metadata: { - name: "new-tunnel" - }, - spec: { - newTunnel: { - name: "new-k8s-tunnel" - }, - size: 2, - cloudflare: { - domain: "example.com", - secret: "cloudflare-secrets", - email: "email@domain.com", - accountName: "", - //accountId: "", - CLOUDFLARE_API_TOKEN: "", - CLOUDFLARE_API_KEY: "" - } - } - } - } + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } + public resourceDefinitions: any = { + Tunnel: { + apiVersion: 'networking.cfargotunnel.com/v1alpha1', + kind: 'Tunnel', + metadata: { + name: 'new-tunnel', + }, + spec: { + newTunnel: { + name: 'new-k8s-tunnel', + }, + size: 2, + cloudflare: { + domain: 'example.com', + secret: 'cloudflare-secrets', + email: 'email@domain.com', + accountName: '', + //accountId: "", + CLOUDFLARE_API_TOKEN: '', + CLOUDFLARE_API_KEY: '', + }, + }, + }, + }; + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } } - diff --git a/server-refactored-v3/src/addons/plugins/cockroachDB.ts b/server-refactored-v3/src/addons/plugins/cockroachDB.ts index 8b58c207..5739acca 100644 --- a/server-refactored-v3/src/addons/plugins/cockroachDB.ts +++ b/server-refactored-v3/src/addons/plugins/cockroachDB.ts @@ -1,114 +1,115 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class Cockroachdb extends Plugin implements IPlugin { - public id: string = 'cockroachdb';//same as operator name - public displayName = 'CockroachDB' - public icon = '/img/addons/CockroachDB.svg' - public install: string = 'kubectl create -f https://operatorhub.io/install/cockroachdb.yaml' - public install_olm: string = 'kubectl create -f https://operatorhub.io/install/cockroachdb.yaml' - public url = 'https://artifacthub.io/packages/olm/community-operators/cockroachdb' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator' - public beta: boolean = true; + public id: string = 'cockroachdb'; //same as operator name + public displayName = 'CockroachDB'; + public icon = '/img/addons/CockroachDB.svg'; + public install: string = + 'kubectl create -f https://operatorhub.io/install/cockroachdb.yaml'; + public install_olm: string = + 'kubectl create -f https://operatorhub.io/install/cockroachdb.yaml'; + public url = + 'https://artifacthub.io/packages/olm/community-operators/cockroachdb'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'Cockroachdb.metadata.name':{ - type: 'text', - label: 'MongoDB Name', - name: 'MongoDB.metadata.name', - required: true, - default: 'mongodbinstance', - description: 'The name of the MongoDB cluster' - }, - 'Cockroachdb.conf.cache':{ - type: 'text', - label: 'Cache Size', - name: 'Cockroachdb.conf.cache', - required: true, - default: '25%', - description: 'Size of the cache' - }, - 'Cockroachdb.conf.max-sql-memory':{ - type: 'text', - label: 'Max SQL Memory', - name: 'Cockroachdb.conf.max-sql-memory', - required: true, - default: '25%', - description: 'Max SQL Memory' - }, - 'Cockroachdb.conf.single-node':{ - type: 'switch', - label: 'Single Node', - name: 'Cockroachdb.conf.single-node', - required: false, - default: false, - description: 'Single Node' - }, - 'Cockroachdb.statefulset.replicas':{ - type: 'number', - label: 'Replicas', - name: 'Cockroachdb.statefulset.replicas', - required: true, - default: 3, - description: 'Number of Replicas' - }, - 'Cockroachdb.spec.storage.persistentVolume.storageSize':{ - type: 'text', - label: 'Sorage Size', - name: 'MongoDB.spec.storage.storageSize', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - 'Cockroachdb.spec.storage.persistentVolume.storageClass':{ - type: 'select-storageclass', - label: 'Sorage Class', - name: 'MongoDB.spec.storage.storageClass', - default: 'standard', - required: true, - description: 'Classname of the storage' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'Cockroachdb.metadata.name': { + type: 'text', + label: 'MongoDB Name', + name: 'MongoDB.metadata.name', + required: true, + default: 'mongodbinstance', + description: 'The name of the MongoDB cluster', + }, + 'Cockroachdb.conf.cache': { + type: 'text', + label: 'Cache Size', + name: 'Cockroachdb.conf.cache', + required: true, + default: '25%', + description: 'Size of the cache', + }, + 'Cockroachdb.conf.max-sql-memory': { + type: 'text', + label: 'Max SQL Memory', + name: 'Cockroachdb.conf.max-sql-memory', + required: true, + default: '25%', + description: 'Max SQL Memory', + }, + 'Cockroachdb.conf.single-node': { + type: 'switch', + label: 'Single Node', + name: 'Cockroachdb.conf.single-node', + required: false, + default: false, + description: 'Single Node', + }, + 'Cockroachdb.statefulset.replicas': { + type: 'number', + label: 'Replicas', + name: 'Cockroachdb.statefulset.replicas', + required: true, + default: 3, + description: 'Number of Replicas', + }, + 'Cockroachdb.spec.storage.persistentVolume.storageSize': { + type: 'text', + label: 'Sorage Size', + name: 'MongoDB.spec.storage.storageSize', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + 'Cockroachdb.spec.storage.persistentVolume.storageClass': { + type: 'select-storageclass', + label: 'Sorage Class', + name: 'MongoDB.spec.storage.storageClass', + default: 'standard', + required: true, + description: 'Classname of the storage', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - - public resourceDefinitions: any = { - 'Cockroachdb': { - apiVersion: "charts.operatorhub.io/v1alpha1", - kind: "Cockroachdb", - metadata: { - name: "cockroachdbinstance", - }, - spec: { - cache: "25%", - 'max-sql-memory': "25%", - 'single-node': false, - statefulset: { - replicas: 3 - }, - storage: { - persistentVolume: { - storageSize: "1Gi", - storageClass: "standard" - } - } - } - } - } + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } + public resourceDefinitions: any = { + Cockroachdb: { + apiVersion: 'charts.operatorhub.io/v1alpha1', + kind: 'Cockroachdb', + metadata: { + name: 'cockroachdbinstance', + }, + spec: { + cache: '25%', + 'max-sql-memory': '25%', + 'single-node': false, + statefulset: { + replicas: 3, + }, + storage: { + persistentVolume: { + storageSize: '1Gi', + storageClass: 'standard', + }, + }, + }, + }, + }; } - - - diff --git a/server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts b/server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts index c67790f9..c03f0d8f 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoCouchDB.ts @@ -1,102 +1,104 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoCouchDB extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'CouchDB' - public icon = '/img/addons/couchdb.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'CouchDB'; + public icon = '/img/addons/couchdb.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoCouchDB.metadata.name':{ - type: 'text', - label: 'Couchdb DB Name', - name: 'metadata.name', - required: true, - default: 'couchdb', - description: 'The name of the Couchdb instance' - }, - 'KuberoCouchDB.spec.couchdb.clusterSize':{ - type: 'number', - label: 'Cluster Size*', - name: 'spec.couchdb.clusterSize', - default: 3, - required: true, - description: 'Number of replicas' - }, - 'KuberoCouchDB.spec.couchdb.persistentVolume.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.couchdb.persistentVolume.storageClass', - default: 'default', - required: true - }, - 'KuberoCouchDB.spec.couchdb.persistentVolume.size':{ - type: 'text', - label: 'Storage Size*', - name: 'spec.couchdb.persistentVolume.size', - default: '8Gi', - required: true, - description: 'Size of the storage' - }, - 'KuberoCouchDB.spec.couchdb.adminUsername':{ - type: 'text', - label: 'Admin Username*', - name: 'spec.couchdb.adminUsername', - default: 'admin', - required: true, - description: 'Admin Username' - }, - 'KuberoCouchDB.spec.couchdb.adminPassword':{ - type: 'text', - label: 'Admin Password*', - name: 'spec.couchdb.auth.rootPassword', - default: '', - required: true, - description: 'Admin Password' - }, - 'KuberoCouchDB.spec.couchdb.adminHash':{ - type: 'text', - label: 'Admin Hash*', - name: 'spec.couchdb.adminHash', - default: '', - required: true, - description: 'Random character string' - }, - 'KuberoCouchDB.spec.couchdb.cookieAuthSecret':{ - type: 'text', - label: 'Cookie Auth Secret*', - name: 'spec.couchdb.cookieAuthSecret', - default: '', - required: true, - description: 'Random character string' - }, - 'KuberoCouchDB.spec.couchdb.couchdbConfig.couchdb.uuid':{ - type: 'text', - label: 'instance UUID*', - name: 'spec.couchdb.couchdbConfig.couchdb.uuid', - default: '', - required: true, - description: 'Random character string' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoCouchDB.metadata.name': { + type: 'text', + label: 'Couchdb DB Name', + name: 'metadata.name', + required: true, + default: 'couchdb', + description: 'The name of the Couchdb instance', + }, + 'KuberoCouchDB.spec.couchdb.clusterSize': { + type: 'number', + label: 'Cluster Size*', + name: 'spec.couchdb.clusterSize', + default: 3, + required: true, + description: 'Number of replicas', + }, + 'KuberoCouchDB.spec.couchdb.persistentVolume.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.couchdb.persistentVolume.storageClass', + default: 'default', + required: true, + }, + 'KuberoCouchDB.spec.couchdb.persistentVolume.size': { + type: 'text', + label: 'Storage Size*', + name: 'spec.couchdb.persistentVolume.size', + default: '8Gi', + required: true, + description: 'Size of the storage', + }, + 'KuberoCouchDB.spec.couchdb.adminUsername': { + type: 'text', + label: 'Admin Username*', + name: 'spec.couchdb.adminUsername', + default: 'admin', + required: true, + description: 'Admin Username', + }, + 'KuberoCouchDB.spec.couchdb.adminPassword': { + type: 'text', + label: 'Admin Password*', + name: 'spec.couchdb.auth.rootPassword', + default: '', + required: true, + description: 'Admin Password', + }, + 'KuberoCouchDB.spec.couchdb.adminHash': { + type: 'text', + label: 'Admin Hash*', + name: 'spec.couchdb.adminHash', + default: '', + required: true, + description: 'Random character string', + }, + 'KuberoCouchDB.spec.couchdb.cookieAuthSecret': { + type: 'text', + label: 'Cookie Auth Secret*', + name: 'spec.couchdb.cookieAuthSecret', + default: '', + required: true, + description: 'Random character string', + }, + 'KuberoCouchDB.spec.couchdb.couchdbConfig.couchdb.uuid': { + type: 'text', + label: 'instance UUID*', + name: 'spec.couchdb.couchdbConfig.couchdb.uuid', + default: '', + required: true, + description: 'Random character string', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts b/server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts index 2a88c944..588eaf21 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoElasticsearch.ts @@ -1,102 +1,104 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoElasticsearch extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'Elasticsearch' - public icon = '/img/addons/elasticsearch.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'Elasticsearch'; + public icon = '/img/addons/elasticsearch.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoElasticsearch.metadata.name':{ - type: 'text', - label: 'Elasticsearch Index Name', - name: 'metadata.name', - required: true, - default: 'elasticsearch', - description: 'The name of the elasticsearch instance' - }, - 'KuberoElasticsearch.spec.elasticsearch.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.elasticsearch.global.storageClass', - default: 'default', - required: true - }, - 'KuberoElasticsearch.spec.elasticsearch.security.elasticPassword':{ - type: 'text', - label: 'User elastic Password*', - name: 'spec.elasticsearch.security.elasticPassword', - default: '', - required: true, - description: 'Password for the user elastic' - }, - 'KuberoElasticsearch.spec.elasticsearch.master.persistence.size':{ - type: 'text', - label: 'Master Storage Size*', - name: 'spec.elasticsearch.master.persistence.size', - default: '8Gi', - required: true, - description: 'Size of the Master storage' - }, - 'KuberoElasticsearch.spec.elasticsearch.master.replicaCount':{ - type: 'number', - label: 'Master Replica Count*', - name: 'spec.elasticsearch.master.replicaCount', - default: 2, - required: true, - description: 'ReplicaCount Number of Master Elasticsearch nodes' - }, - 'KuberoElasticsearch.spec.elasticsearch.data.persistence.size':{ - type: 'text', - label: 'Data Storage Size*', - name: 'spec.spec.elasticsearch.data.persistence.size', - default: '8Gi', - required: true, - description: 'Size of the Data storage' - }, - 'KuberoElasticsearch.spec.elasticsearch.data.replicaCount':{ - type: 'number', - label: 'Data Replica Count*', - name: 'spec.elasticsearch.data.replicaCount', - default: 2, - required: true, - description: 'ReplicaCount Number of Data Elasticsearch nodes' - }, - 'KuberoElasticsearch.spec.elasticsearch.ingest.enabled':{ - type: 'switch', - label: 'Ingest enabled*', - name: 'spec.elasticsearch.ingest.enabled', - default: true, - required: false, - description: 'Ingest enabled' - }, - 'KuberoElasticsearch.spec.elasticsearch.ingest.replicaCount':{ - type: 'number', - label: 'Ingest Replica Count*', - name: 'spec.elasticsearch.ingest.replicaCount', - default: 2, - required: true, - description: 'ReplicaCount Number of Data Elasticsearch nodes' - } - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoElasticsearch.metadata.name': { + type: 'text', + label: 'Elasticsearch Index Name', + name: 'metadata.name', + required: true, + default: 'elasticsearch', + description: 'The name of the elasticsearch instance', + }, + 'KuberoElasticsearch.spec.elasticsearch.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.elasticsearch.global.storageClass', + default: 'default', + required: true, + }, + 'KuberoElasticsearch.spec.elasticsearch.security.elasticPassword': { + type: 'text', + label: 'User elastic Password*', + name: 'spec.elasticsearch.security.elasticPassword', + default: '', + required: true, + description: 'Password for the user elastic', + }, + 'KuberoElasticsearch.spec.elasticsearch.master.persistence.size': { + type: 'text', + label: 'Master Storage Size*', + name: 'spec.elasticsearch.master.persistence.size', + default: '8Gi', + required: true, + description: 'Size of the Master storage', + }, + 'KuberoElasticsearch.spec.elasticsearch.master.replicaCount': { + type: 'number', + label: 'Master Replica Count*', + name: 'spec.elasticsearch.master.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of Master Elasticsearch nodes', + }, + 'KuberoElasticsearch.spec.elasticsearch.data.persistence.size': { + type: 'text', + label: 'Data Storage Size*', + name: 'spec.spec.elasticsearch.data.persistence.size', + default: '8Gi', + required: true, + description: 'Size of the Data storage', + }, + 'KuberoElasticsearch.spec.elasticsearch.data.replicaCount': { + type: 'number', + label: 'Data Replica Count*', + name: 'spec.elasticsearch.data.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of Data Elasticsearch nodes', + }, + 'KuberoElasticsearch.spec.elasticsearch.ingest.enabled': { + type: 'switch', + label: 'Ingest enabled*', + name: 'spec.elasticsearch.ingest.enabled', + default: true, + required: false, + description: 'Ingest enabled', + }, + 'KuberoElasticsearch.spec.elasticsearch.ingest.replicaCount': { + type: 'number', + label: 'Ingest Replica Count*', + name: 'spec.elasticsearch.ingest.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of Data Elasticsearch nodes', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoKafka.ts b/server-refactored-v3/src/addons/plugins/kuberoKafka.ts index 591419ed..40a02c63 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoKafka.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoKafka.ts @@ -1,54 +1,56 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoKafka extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'Kafka' - public icon = '/img/addons/kafka.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'Kafka'; + public icon = '/img/addons/kafka.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoKafka.metadata.name':{ - type: 'text', - label: 'Kafka DB Name', - name: 'metadata.name', - required: true, - default: 'kafka', - description: 'The name of the Kafka instance' - }, - 'KuberoKafka.spec.kafka.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.kafka.global.storageClass', - default: 'default', - required: true - }, - 'KuberoKafka.spec.kafka.persistence.size':{ - type: 'text', - label: 'Storage Size*', - name: 'spec.kafka.persistence.size', - default: '8Gi', - required: true, - description: 'Size of the storage' - } - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoKafka.metadata.name': { + type: 'text', + label: 'Kafka DB Name', + name: 'metadata.name', + required: true, + default: 'kafka', + description: 'The name of the Kafka instance', + }, + 'KuberoKafka.spec.kafka.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.kafka.global.storageClass', + default: 'default', + required: true, + }, + 'KuberoKafka.spec.kafka.persistence.size': { + type: 'text', + label: 'Storage Size*', + name: 'spec.kafka.persistence.size', + default: '8Gi', + required: true, + description: 'Size of the storage', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoMail.ts b/server-refactored-v3/src/addons/plugins/kuberoMail.ts index 1716a104..c0ded898 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoMail.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoMail.ts @@ -1,62 +1,65 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoMail extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'Haraka Mail Server' - public icon = '/img/addons/Haraka.png' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'Haraka Mail Server'; + public icon = '/img/addons/Haraka.png'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoMail.metadata.name':{ - type: 'text', - label: 'Mail Server Name', - name: 'metadata.name', - required: true, - default: 'haraka', - description: 'The name of the mail server instance' - }, - 'KuberoMail.spec.haraka.haraka.env[0].value':{ - type: 'text', - label: 'Hostlist*', - name: 'KuberoMail.spec.haraka.haraka.env[0].value', - default: 'localhost,localhost.kubero.dev', - required: true, - description: 'A comma separated list of hostnames for which the mail server should accept mail' - }, - 'KuberoMail.spec.haraka.haraka.env[1].value':{ - type: 'text', - label: 'Server name*', - name: 'KuberoMail.spec.haraka.haraka.env[1].value', - default: 'info', - required: true, - description: 'Single string for the server name: me' - }, - 'KuberoMail.spec.haraka.haraka.env[6].value':{ - type: 'text', - label: 'Log Level*', - name: 'KuberoMail.spec.haraka.haraka.env[6].value', - default: 'info', - required: true, - description: 'HaraKa log level: info, warn, error, debug' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoMail.metadata.name': { + type: 'text', + label: 'Mail Server Name', + name: 'metadata.name', + required: true, + default: 'haraka', + description: 'The name of the mail server instance', + }, + 'KuberoMail.spec.haraka.haraka.env[0].value': { + type: 'text', + label: 'Hostlist*', + name: 'KuberoMail.spec.haraka.haraka.env[0].value', + default: 'localhost,localhost.kubero.dev', + required: true, + description: + 'A comma separated list of hostnames for which the mail server should accept mail', + }, + 'KuberoMail.spec.haraka.haraka.env[1].value': { + type: 'text', + label: 'Server name*', + name: 'KuberoMail.spec.haraka.haraka.env[1].value', + default: 'info', + required: true, + description: 'Single string for the server name: me', + }, + 'KuberoMail.spec.haraka.haraka.env[6].value': { + type: 'text', + label: 'Log Level*', + name: 'KuberoMail.spec.haraka.haraka.env[6].value', + default: 'info', + required: true, + description: 'HaraKa log level: info, warn, error, debug', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoMemcached.ts b/server-refactored-v3/src/addons/plugins/kuberoMemcached.ts index baef0647..04b50404 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoMemcached.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoMemcached.ts @@ -1,119 +1,121 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoMemcached extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'Memcached' - public icon = '/img/addons/memcached.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = true; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'Memcached'; + public icon = '/img/addons/memcached.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoMemcached.metadata.name':{ - type: 'text', - label: 'Name', - name: 'metadata.name', - required: true, - default: 'memcached', - description: 'The name of the Memcached instance' - }, - 'KuberoMemcached.spec.memcached.architecture':{ - type: 'select', - label: 'Architecture*', - options: ['standalone', 'high-availability'], - name: 'spec.memcached.architecture', - default: 'standalone', - required: true, - description: 'Architecture of the Memcached instance' - }, - 'KuberoMemcached.spec.memcached.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.memcached.global.storageClass', - default: 'default', - required: true - }, - 'KuberoMemcached.spec.memcached.auth.enabled':{ - type: 'switch', - label: 'Enable Authentication', - name: 'spec.memcached.auth.username', - default: true, - required: false, - description: 'Enable Memcached authentication' - }, - 'KuberoMemcached.spec.memcached.auth.username':{ - type: 'text', - label: 'Username', - name: 'spec.memcached.auth.username', - default: '', - required: false, - description: 'Memcached admin user' - }, - 'KuberoMemcached.spec.memcached.auth.password':{ - type: 'text', - label: 'Password', - name: 'spec.memcached.auth.password', - default: '', - required: false, - description: 'Memcached admin password' - }, - 'KuberoMemcached.spec.memcached.resources.requests.memory':{ - type: 'text', - label: 'Memory', - name: 'spec.memcached.resources.requests.memory', - default: '256Mi', - required: true, - description: 'Memcached memory reservation' - }, - 'KuberoMemcached.spec.memcached.replicaCount':{ - type: 'number', - label: 'Replica Count', - name: 'spec.memcached.replicaCount', - default: 1, - required: true, - description: 'Number of Memcached replicas' - }, - 'KuberoMemcached.spec.memcached.autoscaling.enabled':{ - type: 'switch', - label: 'Enable Autoscaling', - name: 'spec.memcached.autoscaling.enabled', - default: true, - required: false, - description: 'Requires Architecture "high-avialable"' - }, - 'KuberoMemcached.spec.memcached.autoscaling.minReplicas':{ - type: 'number', - label: 'Min Replica Count', - name: 'spec.memcached.autoscaling.minReplicas', - default: 3, - required: false, - description: 'Minimal number of Memcached replicas' - }, - 'KuberoMemcached.spec.memcached.autoscaling.maxReplicas':{ - type: 'number', - label: 'Max Replica Count', - name: 'spec.memcached.autoscaling.maxReplicas', - default: 6, - required: false, - description: 'Maximal number of Memcached replicas' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoMemcached.metadata.name': { + type: 'text', + label: 'Name', + name: 'metadata.name', + required: true, + default: 'memcached', + description: 'The name of the Memcached instance', + }, + 'KuberoMemcached.spec.memcached.architecture': { + type: 'select', + label: 'Architecture*', + options: ['standalone', 'high-availability'], + name: 'spec.memcached.architecture', + default: 'standalone', + required: true, + description: 'Architecture of the Memcached instance', + }, + 'KuberoMemcached.spec.memcached.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.memcached.global.storageClass', + default: 'default', + required: true, + }, + 'KuberoMemcached.spec.memcached.auth.enabled': { + type: 'switch', + label: 'Enable Authentication', + name: 'spec.memcached.auth.username', + default: true, + required: false, + description: 'Enable Memcached authentication', + }, + 'KuberoMemcached.spec.memcached.auth.username': { + type: 'text', + label: 'Username', + name: 'spec.memcached.auth.username', + default: '', + required: false, + description: 'Memcached admin user', + }, + 'KuberoMemcached.spec.memcached.auth.password': { + type: 'text', + label: 'Password', + name: 'spec.memcached.auth.password', + default: '', + required: false, + description: 'Memcached admin password', + }, + 'KuberoMemcached.spec.memcached.resources.requests.memory': { + type: 'text', + label: 'Memory', + name: 'spec.memcached.resources.requests.memory', + default: '256Mi', + required: true, + description: 'Memcached memory reservation', + }, + 'KuberoMemcached.spec.memcached.replicaCount': { + type: 'number', + label: 'Replica Count', + name: 'spec.memcached.replicaCount', + default: 1, + required: true, + description: 'Number of Memcached replicas', + }, + 'KuberoMemcached.spec.memcached.autoscaling.enabled': { + type: 'switch', + label: 'Enable Autoscaling', + name: 'spec.memcached.autoscaling.enabled', + default: true, + required: false, + description: 'Requires Architecture "high-avialable"', + }, + 'KuberoMemcached.spec.memcached.autoscaling.minReplicas': { + type: 'number', + label: 'Min Replica Count', + name: 'spec.memcached.autoscaling.minReplicas', + default: 3, + required: false, + description: 'Minimal number of Memcached replicas', + }, + 'KuberoMemcached.spec.memcached.autoscaling.maxReplicas': { + type: 'number', + label: 'Max Replica Count', + name: 'spec.memcached.autoscaling.maxReplicas', + default: 6, + required: false, + description: 'Maximal number of Memcached replicas', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts b/server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts index 7e050050..c68dab03 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoMongoDB.ts @@ -1,118 +1,120 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoMongoDB extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'MongoDB' - public icon = '/img/addons/mongo.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'MongoDB'; + public icon = '/img/addons/mongo.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoMongoDB.metadata.name':{ - type: 'text', - label: 'MongoDB Name', - name: 'metadata.name', - required: true, - default: 'mongodb', - description: 'The name of tht MongoDB instance' - }, - 'KuberoMongoDB.spec.mongodb.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.mongodb.global.storageClass', - default: 'default', - required: true - }, - 'KuberoMongoDB.spec.mongodb.persistence.size':{ - type: 'text', - label: 'Sorage Size*', - name: 'spec.mongodb.persistence.size', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - 'KuberoMongoDB.spec.mongodb.architecture':{ - type: 'select', - label: 'Architecture*', - options: ['standalone', 'replicaset'], - name: 'spec.mongodb.architecture', - default: 'standalone', - required: true - }, - 'KuberoMongoDB.spec.mongodb.auth.databases[0]':{ - type: 'text', - label: 'Database*', - name: 'spec.mongodb.auth.databases[0]', - default: '', - required: true, - description: 'Database Name' - }, - 'KuberoMongoDB.spec.mongodb.auth.rootPassword':{ - type: 'text', - label: 'Root Password*', - name: 'spec.mongodb.auth.rootPassword', - default: '', - required: true, - description: 'Root Password' - }, - 'KuberoMongoDB.spec.mongodb.auth.usernames[0]':{ - type: 'text', - label: 'Username*', - name: 'spec.mongodb.auth.usernames[0]', - default: '', - required: true, - description: 'Additional username' - }, - 'KuberoMongoDB.spec.mongodb.auth.passwords[0]':{ - type: 'text', - label: 'User Password*', - name: 'spec.mongodb.auth.passwords[0]', - default: '', - required: true, - description: 'Password for the additional user' - }, - 'KuberoMongoDB.spec.mongodb.directoryPerDB':{ - type: 'switch', - label: 'Directory per DB', - name: 'spec.mongodb.directoryPerDB', - default: false, - required: false, - description: 'Directory per DB' - }, - 'KuberoMongoDB.spec.mongodb.disableJavascript':{ - type: 'switch', - label: 'Disable Javascript', - name: 'spec.mongodb.disableJavascript', - default: false, - required: false, - description: 'Disable Javascript' - }, - 'KuberoMongoDB.spec.mongodb.replicaCount':{ - type: 'number', - label: 'Replica Count*', - name: 'spec.mongodb.replicaCount', - default: 2, - required: true, - description: 'ReplicaCount Number of MongoDB nodes' - } - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoMongoDB.metadata.name': { + type: 'text', + label: 'MongoDB Name', + name: 'metadata.name', + required: true, + default: 'mongodb', + description: 'The name of tht MongoDB instance', + }, + 'KuberoMongoDB.spec.mongodb.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.mongodb.global.storageClass', + default: 'default', + required: true, + }, + 'KuberoMongoDB.spec.mongodb.persistence.size': { + type: 'text', + label: 'Sorage Size*', + name: 'spec.mongodb.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + 'KuberoMongoDB.spec.mongodb.architecture': { + type: 'select', + label: 'Architecture*', + options: ['standalone', 'replicaset'], + name: 'spec.mongodb.architecture', + default: 'standalone', + required: true, + }, + 'KuberoMongoDB.spec.mongodb.auth.databases[0]': { + type: 'text', + label: 'Database*', + name: 'spec.mongodb.auth.databases[0]', + default: '', + required: true, + description: 'Database Name', + }, + 'KuberoMongoDB.spec.mongodb.auth.rootPassword': { + type: 'text', + label: 'Root Password*', + name: 'spec.mongodb.auth.rootPassword', + default: '', + required: true, + description: 'Root Password', + }, + 'KuberoMongoDB.spec.mongodb.auth.usernames[0]': { + type: 'text', + label: 'Username*', + name: 'spec.mongodb.auth.usernames[0]', + default: '', + required: true, + description: 'Additional username', + }, + 'KuberoMongoDB.spec.mongodb.auth.passwords[0]': { + type: 'text', + label: 'User Password*', + name: 'spec.mongodb.auth.passwords[0]', + default: '', + required: true, + description: 'Password for the additional user', + }, + 'KuberoMongoDB.spec.mongodb.directoryPerDB': { + type: 'switch', + label: 'Directory per DB', + name: 'spec.mongodb.directoryPerDB', + default: false, + required: false, + description: 'Directory per DB', + }, + 'KuberoMongoDB.spec.mongodb.disableJavascript': { + type: 'switch', + label: 'Disable Javascript', + name: 'spec.mongodb.disableJavascript', + default: false, + required: false, + description: 'Disable Javascript', + }, + 'KuberoMongoDB.spec.mongodb.replicaCount': { + type: 'number', + label: 'Replica Count*', + name: 'spec.mongodb.replicaCount', + default: 2, + required: true, + description: 'ReplicaCount Number of MongoDB nodes', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoMysql.ts b/server-refactored-v3/src/addons/plugins/kuberoMysql.ts index 1bb03372..483f22ac 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoMysql.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoMysql.ts @@ -1,94 +1,96 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoMysql extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'MySQL' - public icon = '/img/addons/mysql.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'MySQL'; + public icon = '/img/addons/mysql.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoMysql.metadata.name':{ - type: 'text', - label: 'MySQL DB Name', - name: 'metadata.name', - required: true, - default: 'mysql', - description: 'The name of the MySQL instance' - }, - 'KuberoMysql.spec.mysql.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.mysql.global.storageClass', - default: 'standard', - required: true - }, - 'KuberoMysql.spec.mysql.primary.persistence.size':{ - type: 'text', - label: 'Sorage Size*', - name: 'spec.mysql.primary.persistence.size', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - 'KuberoMysql.spec.mysql.auth.createDatabase':{ - type: 'switch', - label: 'Create a Database*', - name: 'spec.mysql.auth.createDatabase', - default: false, - required: false, - description: 'Create a database on MySQL startup' - }, - 'KuberoMysql.spec.mysql.auth.database':{ - type: 'text', - label: 'Database Name*', - name: 'spec.mysql.auth.database', - default: '', - required: true, - description: 'Name of the database to create' - }, - 'KuberoMysql.spec.mysql.auth.rootPassword':{ - type: 'text', - label: 'Root Password*', - name: 'spec.mysql.auth.rootPassword', - default: '', - required: true, - description: 'Root Password' - }, - 'KuberoMysql.spec.mysql.auth.username':{ - type: 'text', - label: 'Username*', - name: 'spec.mysql.auth.username', - default: '', - required: true, - description: 'Additional username' - }, - 'KuberoMysql.spec.mysql.auth.password':{ - type: 'text', - label: 'User Password*', - name: 'spec.mysql.auth.password', - default: '', - required: true, - description: 'Password for the additional user' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoMysql.metadata.name': { + type: 'text', + label: 'MySQL DB Name', + name: 'metadata.name', + required: true, + default: 'mysql', + description: 'The name of the MySQL instance', + }, + 'KuberoMysql.spec.mysql.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.mysql.global.storageClass', + default: 'standard', + required: true, + }, + 'KuberoMysql.spec.mysql.primary.persistence.size': { + type: 'text', + label: 'Sorage Size*', + name: 'spec.mysql.primary.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + 'KuberoMysql.spec.mysql.auth.createDatabase': { + type: 'switch', + label: 'Create a Database*', + name: 'spec.mysql.auth.createDatabase', + default: false, + required: false, + description: 'Create a database on MySQL startup', + }, + 'KuberoMysql.spec.mysql.auth.database': { + type: 'text', + label: 'Database Name*', + name: 'spec.mysql.auth.database', + default: '', + required: true, + description: 'Name of the database to create', + }, + 'KuberoMysql.spec.mysql.auth.rootPassword': { + type: 'text', + label: 'Root Password*', + name: 'spec.mysql.auth.rootPassword', + default: '', + required: true, + description: 'Root Password', + }, + 'KuberoMysql.spec.mysql.auth.username': { + type: 'text', + label: 'Username*', + name: 'spec.mysql.auth.username', + default: '', + required: true, + description: 'Additional username', + }, + 'KuberoMysql.spec.mysql.auth.password': { + type: 'text', + label: 'User Password*', + name: 'spec.mysql.auth.password', + default: '', + required: true, + description: 'Password for the additional user', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts b/server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts index 135a462a..39cf3c02 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoPostgresql.ts @@ -1,86 +1,89 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoPostgresql extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'Postgresql' - public icon = '/img/addons/pgsql.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'Postgresql'; + public icon = '/img/addons/pgsql.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoPostgresql.metadata.name':{ - type: 'text', - label: 'PostgreSQL Instance Name', - name: 'metadata.name', - required: true, - default: 'postgresql', - description: 'The name of the PostgreSQL instance' - }, - 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.postgresPassword':{ - type: 'text', - label: 'Postgres admin Password*', - name: 'spec.postgresql.global.postgresql.auth.postgresPassword', - default: '', - required: true, - description: 'Password for the "postgres" admin user' - }, - 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.username':{ - type: 'text', - label: 'Username*', - name: 'spec.postgresql.global.postgresql.auth.username', - default: '', - required: true, - description: 'Username for an additional user to create' - }, - 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.password':{ - type: 'text', - label: 'User Password*', - name: 'spec.postgresql.global.postgresql.auth.password', - default: '', - required: true, - description: 'Password for an additional user to create' - }, - 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.database':{ - type: 'text', - label: 'Database*', - name: 'spec.postgresql.global.postgresql.auth.database', - default: 'postgresql', - required: true, - description: 'Name for a custom database to create' - }, - 'KuberoPostgresql.spec.postgresql.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.postgresql.global.storageClass', - default: 'default', - required: true - }, - 'KuberoPostgresql.spec.postgresql.primary.persistence.size':{ - type: 'text', - label: 'Sorage Size*', - name: 'spec.postgresql.primary.persistence.size', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoPostgresql.metadata.name': { + type: 'text', + label: 'PostgreSQL Instance Name', + name: 'metadata.name', + required: true, + default: 'postgresql', + description: 'The name of the PostgreSQL instance', + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.postgresPassword': + { + type: 'text', + label: 'Postgres admin Password*', + name: 'spec.postgresql.global.postgresql.auth.postgresPassword', + default: '', + required: true, + description: 'Password for the "postgres" admin user', + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.username': { + type: 'text', + label: 'Username*', + name: 'spec.postgresql.global.postgresql.auth.username', + default: '', + required: true, + description: 'Username for an additional user to create', + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.password': { + type: 'text', + label: 'User Password*', + name: 'spec.postgresql.global.postgresql.auth.password', + default: '', + required: true, + description: 'Password for an additional user to create', + }, + 'KuberoPostgresql.spec.postgresql.global.postgresql.auth.database': { + type: 'text', + label: 'Database*', + name: 'spec.postgresql.global.postgresql.auth.database', + default: 'postgresql', + required: true, + description: 'Name for a custom database to create', + }, + 'KuberoPostgresql.spec.postgresql.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.postgresql.global.storageClass', + default: 'default', + required: true, + }, + 'KuberoPostgresql.spec.postgresql.primary.persistence.size': { + type: 'text', + label: 'Sorage Size*', + name: 'spec.postgresql.primary.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts b/server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts index 532c81d2..4b76f1cf 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoRabbitMQ.ts @@ -1,94 +1,96 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoRabbitMQ extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'RabbitMQ' - public icon = '/img/addons/RabbitMQ.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = true; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'RabbitMQ'; + public icon = '/img/addons/RabbitMQ.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoRabbitMQ.metadata.name':{ - type: 'text', - label: 'RabbitMQ Instance Name', - name: 'metadata.name', - required: true, - default: 'rabbitmq', - description: 'The name of the PostgreSQL instance' - }, - 'KuberoRabbitMQ.spec.rabbitmq.auth.username':{ - type: 'text', - label: 'User Name*', - name: 'spec.rabbitmq.auth.username', - default: '', - required: true, - description: 'Username' - }, - 'KuberoRabbitMQ.spec.rabbitmq.global.auth.password':{ - type: 'text', - label: 'User Password', - name: 'spec.rabbitmq.auth.password', - default: '', - required: true, - description: 'Password' - }, - 'KuberoRabbitMQ.spec.rabbitmq.global.rabbitmq.auth.securepassword':{ - type: 'text', - label: 'Secure Password', - name: 'spec.rabbitmq.global.rabbitmq.auth.securepassword', - default: '', - required: false, - description: 'Secure Password' - }, - 'KuberoRabbitMQ.spec.rabbitmq.global.rabbitmq.auth.erlangCookie':{ - type: 'text', - label: 'Erlang Cookie', - name: 'spec.rabbitmq.global.rabbitmq.auth.erlangCookie', - default: '', - required: false, - description: 'Erlang Cookie' - }, - 'KuberoRabbitMQ.spec.rabbitmq.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.rabbitmq.global.storageClass', - default: 'default', - required: true - }, - 'KuberoRabbitMQ.spec.rabbitmq.maxAvailableSchedulers':{ - type: 'number', - label: 'Max Available Schedulers', - name: 'spec.rabbitmq.maxAvailableSchedulers', - default: '', - required: false, - description: 'Max available schedulers' - }, - 'KuberoRabbitMQ.spec.rabbitmq.onlineSchedulers':{ - type: 'number', - label: 'Online Schedulers', - name: 'spec.rabbitmq.onlineSchedulers', - default: '', - required: false, - description: 'Online schedulers' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoRabbitMQ.metadata.name': { + type: 'text', + label: 'RabbitMQ Instance Name', + name: 'metadata.name', + required: true, + default: 'rabbitmq', + description: 'The name of the PostgreSQL instance', + }, + 'KuberoRabbitMQ.spec.rabbitmq.auth.username': { + type: 'text', + label: 'User Name*', + name: 'spec.rabbitmq.auth.username', + default: '', + required: true, + description: 'Username', + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.auth.password': { + type: 'text', + label: 'User Password', + name: 'spec.rabbitmq.auth.password', + default: '', + required: true, + description: 'Password', + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.rabbitmq.auth.securepassword': { + type: 'text', + label: 'Secure Password', + name: 'spec.rabbitmq.global.rabbitmq.auth.securepassword', + default: '', + required: false, + description: 'Secure Password', + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.rabbitmq.auth.erlangCookie': { + type: 'text', + label: 'Erlang Cookie', + name: 'spec.rabbitmq.global.rabbitmq.auth.erlangCookie', + default: '', + required: false, + description: 'Erlang Cookie', + }, + 'KuberoRabbitMQ.spec.rabbitmq.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.rabbitmq.global.storageClass', + default: 'default', + required: true, + }, + 'KuberoRabbitMQ.spec.rabbitmq.maxAvailableSchedulers': { + type: 'number', + label: 'Max Available Schedulers', + name: 'spec.rabbitmq.maxAvailableSchedulers', + default: '', + required: false, + description: 'Max available schedulers', + }, + 'KuberoRabbitMQ.spec.rabbitmq.onlineSchedulers': { + type: 'number', + label: 'Online Schedulers', + name: 'spec.rabbitmq.onlineSchedulers', + default: '', + required: false, + description: 'Online schedulers', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/kuberoRedis.ts b/server-refactored-v3/src/addons/plugins/kuberoRedis.ts index ab652492..579f14a7 100644 --- a/server-refactored-v3/src/addons/plugins/kuberoRedis.ts +++ b/server-refactored-v3/src/addons/plugins/kuberoRedis.ts @@ -1,78 +1,80 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class KuberoRedis extends Plugin implements IPlugin { - public id: string = 'kubero-operator';//same as operator name - public displayName = 'Redis' - public icon = '/img/addons/redis.svg' - public install: string = '' - public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator' - public beta: boolean = false; + public id: string = 'kubero-operator'; //same as operator name + public displayName = 'Redis'; + public icon = '/img/addons/redis.svg'; + public install: string = ''; + public url = + 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; + public beta: boolean = false; - public formfields: {[key: string]: IPluginFormFields} = { - 'KuberoRedis.metadata.name':{ - type: 'text', - label: 'Redis Cluster Name', - name: 'metadata.name', - required: true, - default: 'redis', - description: 'The name of the redis instance' - }, - 'KuberoRedis.spec.redis.replica.replicaCount':{ - type: 'number', - label: 'Replica Count*', - name: 'spec.redis.replica.replicaCount', - default: '3', - required: true, - description: 'Number of replicas' - }, - 'KuberoRedis.spec.redis.global.storageClass':{ - type: 'select-storageclass', - label: 'Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.redis.global.storageClass', - default: 'default', - required: true - }, - 'KuberoRedis.spec.redis.master.persistence.size':{ - type: 'text', - label: 'Master Sorage Size*', - name: 'spec.redis.master.persistence.size', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - 'KuberoRedis.spec.redis.replica.persistence.size':{ - type: 'text', - label: 'Replica Sorage Size*', - name: 'spec.redis.replica.persistence.size', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - 'KuberoRedis.spec.redis.global.redis.password':{ - type: 'text', - label: 'Password*', - name: 'spec.redis.global.redis.password', - default: '', - required: true, - description: 'Password' - } - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'KuberoRedis.metadata.name': { + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'redis', + description: 'The name of the redis instance', + }, + 'KuberoRedis.spec.redis.replica.replicaCount': { + type: 'number', + label: 'Replica Count*', + name: 'spec.redis.replica.replicaCount', + default: '3', + required: true, + description: 'Number of replicas', + }, + 'KuberoRedis.spec.redis.global.storageClass': { + type: 'select-storageclass', + label: 'Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.redis.global.storageClass', + default: 'default', + required: true, + }, + 'KuberoRedis.spec.redis.master.persistence.size': { + type: 'text', + label: 'Master Sorage Size*', + name: 'spec.redis.master.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + 'KuberoRedis.spec.redis.replica.persistence.size': { + type: 'text', + label: 'Replica Sorage Size*', + name: 'spec.redis.replica.persistence.size', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + 'KuberoRedis.spec.redis.global.redis.password': { + type: 'text', + label: 'Password*', + name: 'spec.redis.global.redis.password', + default: '', + required: true, + description: 'Password', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/minio.ts b/server-refactored-v3/src/addons/plugins/minio.ts index acbad164..68475be7 100644 --- a/server-refactored-v3/src/addons/plugins/minio.ts +++ b/server-refactored-v3/src/addons/plugins/minio.ts @@ -1,207 +1,207 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class Tenant extends Plugin implements IPlugin { - public id: string = 'minio-operator';//same as operator name - public displayName = 'Minio' - public icon = '/img/addons/Minio.png' - public install: string = 'kubectl create -f https://operatorhub.io/install/stable/minio-operator.yaml -n operators' - public url = 'https://artifacthub.io/packages/olm/community-operators/minio-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/minio-operator' - public beta: boolean = true; + public id: string = 'minio-operator'; //same as operator name + public displayName = 'Minio'; + public icon = '/img/addons/Minio.png'; + public install: string = + 'kubectl create -f https://operatorhub.io/install/stable/minio-operator.yaml -n operators'; + public url = + 'https://artifacthub.io/packages/olm/community-operators/minio-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/minio-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'Tenant.metadata.name':{ - type: 'text', - label: 'Minio Cluster Name', - name: 'metadata.name', - required: true, - default: 'storage-lite', - description: 'The name of the Minio cluster' - }, - 'Tenant.spec.pools[0].servers':{ - type: 'number', - label: 'Clustersize', - name: 'Tenant.spec.pools[0].servers', - default: 4, - required: true, - description: 'Number of pool servers' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'Tenant.metadata.name': { + type: 'text', + label: 'Minio Cluster Name', + name: 'metadata.name', + required: true, + default: 'storage-lite', + description: 'The name of the Minio cluster', + }, + 'Tenant.spec.pools[0].servers': { + type: 'number', + label: 'Clustersize', + name: 'Tenant.spec.pools[0].servers', + default: 4, + required: true, + description: 'Number of pool servers', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = { - // TODO requires to deploy some secrets - /* + protected additionalResourceDefinitions: object = { + // TODO requires to deploy some secrets + /* E1019 13:11:37.950072 1 main-controller.go:584] error syncing 'another-production/storage-lite': secrets "storage-configuration" not found 2022/10/19 13:11:38 http: TLS handshake error from 10.244.0.1:57646: remote error: tls: bad certificate */ - Tenant: { - apiVersion: "minio.min.io/v2", - kind: "Tenant", - metadata: { - annotations: { - 'prometheus.io/path': "/minio/v2/metrics/cluster", - 'prometheus.io/port': "9000", - 'prometheus.io/scrape': "true" - }, - labels: { - app: "minio" - }, - name: "storage-lite", + Tenant: { + apiVersion: 'minio.min.io/v2', + kind: 'Tenant', + metadata: { + annotations: { + 'prometheus.io/path': '/minio/v2/metrics/cluster', + 'prometheus.io/port': '9000', + 'prometheus.io/scrape': 'true', + }, + labels: { + app: 'minio', + }, + name: 'storage-lite', + }, + spec: { + certConfig: {}, + configuration: { + name: 'storage-configuration', + }, + env: [], + externalCaCertSecret: [], + externalCertSecret: [], + externalClientCertSecrets: [], + features: { + bucketDNS: false, + domains: {}, + }, + image: + 'quay.io/minio/minio@sha256:c3d20bc2ea08477248c15f932822f932b092b658c5692a9c9f4d88abcf556ed7', + imagePullSecret: {}, + log: { + affinity: { + nodeAffinity: {}, + podAffinity: {}, + podAntiAffinity: {}, + }, + annotations: {}, + audit: { + diskCapacityGB: 1, + }, + db: { + affinity: { + nodeAffinity: {}, + podAffinity: {}, + podAntiAffinity: {}, }, - spec: { - certConfig: {}, - configuration: { - name: "storage-configuration" - }, - env: [], - externalCaCertSecret: [], - externalCertSecret: [], - externalClientCertSecrets: [], - features: { - bucketDNS: false, - domains: {} - }, - image: "quay.io/minio/minio@sha256:c3d20bc2ea08477248c15f932822f932b092b658c5692a9c9f4d88abcf556ed7", - imagePullSecret: {}, - log: { - affinity: { - nodeAffinity: {}, - podAffinity: {}, - podAntiAffinity: {} - }, - annotations: {}, - audit: { - diskCapacityGB: 1 - }, - db: { - affinity: { - nodeAffinity: {}, - podAffinity: {}, - podAntiAffinity: {} - }, - annotations: {}, - env: [], - image: "", - initimage: "", - labels: {}, - nodeSelector: {}, - resources: {}, - securityContext: { - fsGroup: 999, - runAsGroup: 999, - runAsNonRoot: true, - runAsUser: 999 + annotations: {}, + env: [], + image: '', + initimage: '', + labels: {}, + nodeSelector: {}, + resources: {}, + securityContext: { + fsGroup: 999, + runAsGroup: 999, + runAsNonRoot: true, + runAsUser: 999, + }, + serviceAccountName: '', + tolerations: [], + volumeClaimTemplate: { + metadata: {}, + spec: { + accessModes: ['ReadWriteOnce'], + resources: { + requests: { + storage: '1Gi', }, - serviceAccountName: "", - tolerations: [], - volumeClaimTemplate: { - metadata: {}, - spec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "1Gi" - } - }, - storageClassName: "standard" - } - } - }, - env: [], - image: "", - labels: {}, - nodeSelector: {}, - resources: {}, - securityContext: { - fsGroup: 1000, - runAsGroup: 1000, - runAsNonRoot: true, - runAsUser: 1000 }, - serviceAccountName: "", - tolerations: [] + storageClassName: 'standard', + }, + }, + }, + env: [], + image: '', + labels: {}, + nodeSelector: {}, + resources: {}, + securityContext: { + fsGroup: 1000, + runAsGroup: 1000, + runAsNonRoot: true, + runAsUser: 1000, + }, + serviceAccountName: '', + tolerations: [], + }, + mountPath: '/export', + podManagementPolicy: 'Parallel', + pools: [ + { + name: 'pool-0', + servers: 4, + volumeClaimTemplate: { + metadata: { + name: 'data', }, - mountPath: "/export", - podManagementPolicy: "Parallel", - pools: [ - { - name: "pool-0", - servers: 4, - volumeClaimTemplate: { - metadata: { - name: "data" - }, - spec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "2Gi" - } - } - } + spec: { + accessModes: ['ReadWriteOnce'], + resources: { + requests: { + storage: '2Gi', }, - volumesPerServer: 2 - } - ], - priorityClassName: "", - prometheus: { - affinity: { - nodeAffinity: {}, - podAffinity: {}, - podAntiAffinity: {} }, - annotations: {}, - diskCapacityGB: 1, - env: [], - image: "", - initimage: "", - labels: {}, - nodeSelector: {}, - resources: {}, - securityContext: { - fsGroup: 1000, - runAsGroup: 1000, - runAsNonRoot: true, - runAsUser: 1000 - }, - serviceAccountName: "", - sidecarimage: "", - storageClassName: "standard" - }, - requestAutoCert: true, - serviceAccountName: "", - serviceMetadata: { - consoleServiceAnnotations: {}, - consoleServiceLabels: {}, - minioServiceAnnotations: {}, - minioServiceLabels: {} }, - subPath: "", - users: [ - { - name: "storage-user" - } - ] - } - } - } - - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } + }, + volumesPerServer: 2, + }, + ], + priorityClassName: '', + prometheus: { + affinity: { + nodeAffinity: {}, + podAffinity: {}, + podAntiAffinity: {}, + }, + annotations: {}, + diskCapacityGB: 1, + env: [], + image: '', + initimage: '', + labels: {}, + nodeSelector: {}, + resources: {}, + securityContext: { + fsGroup: 1000, + runAsGroup: 1000, + runAsNonRoot: true, + runAsUser: 1000, + }, + serviceAccountName: '', + sidecarimage: '', + storageClassName: 'standard', + }, + requestAutoCert: true, + serviceAccountName: '', + serviceMetadata: { + consoleServiceAnnotations: {}, + consoleServiceLabels: {}, + minioServiceAnnotations: {}, + minioServiceLabels: {}, + }, + subPath: '', + users: [ + { + name: 'storage-user', + }, + ], + }, + }, + }; -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/mongoDB.ts b/server-refactored-v3/src/addons/plugins/mongoDB.ts index 25ff80ab..e5bb9de3 100644 --- a/server-refactored-v3/src/addons/plugins/mongoDB.ts +++ b/server-refactored-v3/src/addons/plugins/mongoDB.ts @@ -1,83 +1,86 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class MongoDB extends Plugin implements IPlugin { - public id: string = 'mongodb-operator';//same as operator name - public displayName = 'Percona MongoDB' - public icon = '/img/addons/mongo.svg' - public install: string = 'kubectl create -f https://operatorhub.io/install/mongodb-operator.yaml' - public url = 'https://artifacthub.io/packages/olm/community-operators/mongodb-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator' - public beta: boolean = true; + public id: string = 'mongodb-operator'; //same as operator name + public displayName = 'Percona MongoDB'; + public icon = '/img/addons/mongo.svg'; + public install: string = + 'kubectl create -f https://operatorhub.io/install/mongodb-operator.yaml'; + public url = + 'https://artifacthub.io/packages/olm/community-operators/mongodb-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'MongoDB.metadata.name':{ - type: 'text', - label: 'MongoDB Name', - name: 'MongoDB.metadata.name', - required: true, - default: 'mongodbinstance', - description: 'The name of the MongoDB cluster' - }, - 'MongoDB.spec.storage.storageSize':{ - type: 'text', - label: 'Sorage Size', - name: 'MongoDB.spec.storage.storageSize', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - 'MongoDB.spec.storage.storageClass':{ - type: 'text', - label: 'Sorage Class', - name: 'MongoDB.spec.storage.storageClass', - default: 'standard', - required: true, - description: 'Classname of the storage' - }, - 'mongodbSecret.stringData.password':{ - type: 'text', - label: 'MongoDB Password', - name: 'mongodbSecret.stringData.password', - default: 'changeMe', - required: true, - description: 'Password for MongoDB' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'MongoDB.metadata.name': { + type: 'text', + label: 'MongoDB Name', + name: 'MongoDB.metadata.name', + required: true, + default: 'mongodbinstance', + description: 'The name of the MongoDB cluster', + }, + 'MongoDB.spec.storage.storageSize': { + type: 'text', + label: 'Sorage Size', + name: 'MongoDB.spec.storage.storageSize', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + 'MongoDB.spec.storage.storageClass': { + type: 'text', + label: 'Sorage Class', + name: 'MongoDB.spec.storage.storageClass', + default: 'standard', + required: true, + description: 'Classname of the storage', + }, + 'mongodbSecret.stringData.password': { + type: 'text', + label: 'MongoDB Password', + name: 'mongodbSecret.stringData.password', + default: 'changeMe', + required: true, + description: 'Password for MongoDB', + }, + }; - public env: any[] = [] + public env: any[] = []; - //https://www.convertsimple.com/convert-yaml-to-javascript-object/ - protected additionalResourceDefinitions: Object = { - mongodbSecret: { - apiVersion: "v1", - stringData: { - // TODO - generate a random password or make it configurable - password: "test", - }, - kind: "Secret", - metadata: { - annotations: { - 'meta.helm.sh/release-name': "test", - 'meta.helm.sh/release-namespace': "kubero-dev" - }, - labels: { - 'app.kubernetes.io/managed-by': "Kubero" - }, - name: "mongodb-secret", - }, - type: "Opaque" - } - } - - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } + //https://www.convertsimple.com/convert-yaml-to-javascript-object/ + protected additionalResourceDefinitions: object = { + mongodbSecret: { + apiVersion: 'v1', + stringData: { + // TODO - generate a random password or make it configurable + password: 'test', + }, + kind: 'Secret', + metadata: { + annotations: { + 'meta.helm.sh/release-name': 'test', + 'meta.helm.sh/release-namespace': 'kubero-dev', + }, + labels: { + 'app.kubernetes.io/managed-by': 'Kubero', + }, + name: 'mongodb-secret', + }, + type: 'Opaque', + }, + }; -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/mongoDBCluster.ts b/server-refactored-v3/src/addons/plugins/mongoDBCluster.ts index 7864d6a2..df57208d 100644 --- a/server-refactored-v3/src/addons/plugins/mongoDBCluster.ts +++ b/server-refactored-v3/src/addons/plugins/mongoDBCluster.ts @@ -1,54 +1,57 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class MongoDBCluster extends Plugin implements IPlugin { - public id: string = 'mongodb-operator';//same as operator name - public displayName = 'Percona MongoDB Cluster' - public icon = '/img/addons/mongo.svg' - public install: string = 'kubectl create -f https://operatorhub.io/install/mongodb-operator.yaml' - public url = 'https://artifacthub.io/packages/olm/community-operators/mongodb-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator' - public beta: boolean = true; + public id: string = 'mongodb-operator'; //same as operator name + public displayName = 'Percona MongoDB Cluster'; + public icon = '/img/addons/mongo.svg'; + public install: string = + 'kubectl create -f https://operatorhub.io/install/mongodb-operator.yaml'; + public url = + 'https://artifacthub.io/packages/olm/community-operators/mongodb-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/mongodb-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'MongoDBCluster.metadata.name':{ - type: 'text', - label: 'MongoDB Cluster Name', - name: 'metadata.name', - required: true, - default: 'mongodb-cluster', - description: 'The name of the MongoDB cluster' - }, - 'MongoDBCluster.spec.clusterSize':{ - type: 'number', - label: 'Clustersize', - name: 'spec.clusterSize', - default: 3, - required: true, - description: 'Number of Replicasets MongoDB instances in the cluster' - }, - 'MongoDBCluster.spec.storage.storageSize':{ - type: 'text', - label: 'Sorage Size', - name: 'spec.storage.storageSize', - default: '1Gi', - required: true, - description: 'Size of the storage' - }, - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'MongoDBCluster.metadata.name': { + type: 'text', + label: 'MongoDB Cluster Name', + name: 'metadata.name', + required: true, + default: 'mongodb-cluster', + description: 'The name of the MongoDB cluster', + }, + 'MongoDBCluster.spec.clusterSize': { + type: 'number', + label: 'Clustersize', + name: 'spec.clusterSize', + default: 3, + required: true, + description: 'Number of Replicasets MongoDB instances in the cluster', + }, + 'MongoDBCluster.spec.storage.storageSize': { + type: 'text', + label: 'Sorage Size', + name: 'spec.storage.storageSize', + default: '1Gi', + required: true, + description: 'Size of the storage', + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/plugin.interface.ts b/server-refactored-v3/src/addons/plugins/plugin.interface.ts index 7e017cf4..babb47a7 100644 --- a/server-refactored-v3/src/addons/plugins/plugin.interface.ts +++ b/server-refactored-v3/src/addons/plugins/plugin.interface.ts @@ -1,25 +1,25 @@ export interface IPluginFormFields { - type: 'text' | 'number' |'switch' | 'select' | 'select-storageclass', - label: string, - name: string, - required: boolean, - options?: string[], - default: string | number | boolean, - description?: string, + type: 'text' | 'number' | 'switch' | 'select' | 'select-storageclass'; + label: string; + name: string; + required: boolean; + options?: string[]; + default: string | number | boolean; + description?: string; } export interface IPlugin { - id: string - enabled: boolean, - beta: boolean, + id: string; + enabled: boolean; + beta: boolean; version: { - latest: string, - installed: string, - }, - description: string, - install: string, - formfields: {[key: string]: IPluginFormFields}, + latest: string; + installed: string; + }; + description: string; + install: string; + formfields: { [key: string]: IPluginFormFields }; //crd: KubernetesObject, - resourceDefinitions: any, + resourceDefinitions: any; artifact_url: string; -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/addons/plugins/plugin.ts b/server-refactored-v3/src/addons/plugins/plugin.ts index aa5adba4..51bee98f 100644 --- a/server-refactored-v3/src/addons/plugins/plugin.ts +++ b/server-refactored-v3/src/addons/plugins/plugin.ts @@ -1,179 +1,183 @@ import axios from 'axios'; -import { KubernetesListObject, KubernetesObject } from '@kubernetes/client-node' +import { + KubernetesListObject, + KubernetesObject, +} from '@kubernetes/client-node'; import { Logger } from '@nestjs/common'; export interface IPluginFormFields { - type: 'text' | 'number' |'switch' | 'select' | 'select-storageclass', - label: string, - name: string, - required: boolean, - options?: string[], - default: string | number | boolean, - description?: string, + type: 'text' | 'number' | 'switch' | 'select' | 'select-storageclass'; + label: string; + name: string; + required: boolean; + options?: string[]; + default: string | number | boolean; + description?: string; } export interface IPlugin { - id: string - enabled: boolean, - beta: boolean, - version: { - latest: string, - installed: string, - }, - description: string, - install: string, - formfields: {[key: string]: IPluginFormFields}, - //crd: KubernetesObject, - resourceDefinitions: any, - artifact_url: string; + id: string; + enabled: boolean; + beta: boolean; + version: { + latest: string; + installed: string; + }; + description: string; + install: string; + formfields: { [key: string]: IPluginFormFields }; + //crd: KubernetesObject, + resourceDefinitions: any; + artifact_url: string; } export abstract class Plugin { - public plugin?: any; - public id: string = ''; //same as operator name - public enabled: boolean = false; // true if installed - public version: { - latest:string, - installed: string - } = { - 'latest': '0.0.0', // version fetched from artifacthub - 'installed': '0.0.0', // loaded if avialable from local operators - }; - public displayName: string = ''; - public description: string = ''; - public maintainers: Object[] = []; - public links: Object[] = []; - public readme: string = ''; - //public crd: KubernetesObject = {}; // ExampleCRD which will be used as template - protected additionalResourceDefinitions: Object = {}; - public resourceDefinitions: any = {}; // List of CRD to apply - - public artifact_url: string = ''; // Example: https://artifacthub.io/api/v1/packages/olm/community-operators/postgresql - private artefact_data: any = {}; - private operator_data: any = {}; - public kind: string; - - private readonly logger = new Logger(Plugin.name); - - constructor() { - this.kind = this.constructor.name; + public plugin?: any; + public id: string = ''; //same as operator name + public enabled: boolean = false; // true if installed + public version: { + latest: string; + installed: string; + } = { + latest: '0.0.0', // version fetched from artifacthub + installed: '0.0.0', // loaded if avialable from local operators + }; + public displayName: string = ''; + public description: string = ''; + public maintainers: object[] = []; + public links: object[] = []; + public readme: string = ''; + //public crd: KubernetesObject = {}; // ExampleCRD which will be used as template + protected additionalResourceDefinitions: object = {}; + public resourceDefinitions: any = {}; // List of CRD to apply + + public artifact_url: string = ''; // Example: https://artifacthub.io/api/v1/packages/olm/community-operators/postgresql + private artefact_data: any = {}; + private operator_data: any = {}; + public kind: string; + + private readonly logger = new Logger(Plugin.name); + + constructor() { + this.kind = this.constructor.name; + } + + public async init(availableCRDs: any) { + // load data from local Operators + this.operator_data = this.loadOperatorData(availableCRDs); + + // load data from artifacthub + await this.loadMetadataFromArtefacthub(); + + // load CRD from artefacthub, or alterantively from local operator, as a fallback use the CRD from the plugin + this.loadCRD(); + + this.loadAdditionalResourceDefinitions(); + + if (this.enabled) { + this.logger.log('✅ ' + this.id + ' ' + this.constructor.name); + //this.logger.debug(this.resourceDefinitions) // debug CRD + } else { + this.logger.log('☑️ ' + this.id + ' ' + this.constructor.name); } - - public async init(availableCRDs: any) { - - // load data from local Operators - this.operator_data = this.loadOperatorData(availableCRDs); - - // load data from artifacthub - await this.loadMetadataFromArtefacthub(); - - // load CRD from artefacthub, or alterantively from local operator, as a fallback use the CRD from the plugin - this.loadCRD(); - - this.loadAdditionalResourceDefinitions(); - - if (this.enabled) { - this.logger.log("✅ "+this.id + ' ' +this.constructor.name) - //this.logger.debug(this.resourceDefinitions) // debug CRD - } else { - this.logger.log("☑️ "+this.id + ' ' +this.constructor.name) - } - - + } + + private async loadMetadataFromArtefacthub() { + const response = await axios.get(this.artifact_url).catch((error) => { + this.logger.debug( + ' failed loading data from artifacthub for ' + this.id, + ); + //console.log(error); + }); + + // set artifact hub values + if (response?.data && response.data.description) { + //this.displayName = response?.data.displayName; // use the name from the plugin + this.description = response.data.description; + this.maintainers = response.data.maintainers; + this.links = response.data.links; + this.readme = response.data.readme; + this.version.latest = response.data.version; + this.artefact_data = response.data; + } else { + this.logger.debug(' No artefact.io data found for ' + this.id); } + } - private async loadMetadataFromArtefacthub() { - const response = await axios.get(this.artifact_url) - .catch(error => { - this.logger.debug(' failed loading data from artifacthub for '+this.id) - //console.log(error); - } + private loadCRD() { + if (this.resourceDefinitions[this.kind] !== undefined) { + // CRD already loaded from operator + return; + } + if (this.artefact_data.crds === undefined) { + this.logger.debug(' No CRDs defined in artefacthub for ' + this.id); + this.loadCRDFromOperatorData(); + return; + } else { + this.loadCRDFromArtefacthubData(); + } + } + + private loadCRDFromArtefacthubData() { + for (const artefactCRD of this.artefact_data.crds) { + if (artefactCRD.kind === this.kind) { + // search in artefact data for the crd + const exampleCRD = this.artefact_data.crds_examples.find( + (crd: any) => crd.kind === artefactCRD.kind, ); - // set artifact hub values - if (response?.data && response.data.description) { - //this.displayName = response?.data.displayName; // use the name from the plugin - this.description = response.data.description; - this.maintainers = response.data.maintainers; - this.links = response.data.links; - this.readme = response.data.readme; - this.version.latest = response.data.version; - this.artefact_data = response.data; - } else { - this.logger.debug(" No artefact.io data found for "+this.id) - } - - } + this.resourceDefinitions[this.kind] = exampleCRD; - private loadCRD() { - if (this.resourceDefinitions[this.kind] !== undefined) { - // CRD already loaded from operator - return; + //this.displayName = artefactCRD.displayName; // use the name from the plugin + if (artefactCRD.description.length > this.description.length) { + this.description = artefactCRD.description; // use the description from the CRD } - if (this.artefact_data.crds === undefined) { - this.logger.debug(" No CRDs defined in artefacthub for "+this.id) - this.loadCRDFromOperatorData(); - return; - } else { - this.loadCRDFromArtefacthubData(); - } - } - private loadCRDFromArtefacthubData() { - for (const artefactCRD of this.artefact_data.crds) { - if (artefactCRD.kind === this.kind) { - // search in artefact data for the crd - let exampleCRD = this.artefact_data.crds_examples.find((crd: any) => crd.kind === artefactCRD.kind); - - this.resourceDefinitions[this.kind] = exampleCRD; - - //this.displayName = artefactCRD.displayName; // use the name from the plugin - if (artefactCRD.description.length > this.description.length) { - this.description = artefactCRD.description; // use the description from the CRD - } - - break; - } - } + break; + } } + } - private loadCRDFromOperatorData() { - if (this.operator_data === undefined) { - this.logger.error("No CRDs defined in operator for "+this.id) - return; - } - - const operatorCRDList = this.operator_data.metadata.annotations['alm-examples']; + private loadCRDFromOperatorData() { + if (this.operator_data === undefined) { + this.logger.error('No CRDs defined in operator for ' + this.id); + return; + } - if (operatorCRDList === undefined) { - this.logger.error("No CRDs defined in operator for "+this.id) - return; - } + const operatorCRDList = + this.operator_data.metadata.annotations['alm-examples']; - for (const op of JSON.parse(operatorCRDList)) { - if (op.kind === this.constructor.name) { - //this.crd = op; - this.resourceDefinitions[op.kind] = op; - break; - } - } + if (operatorCRDList === undefined) { + this.logger.error('No CRDs defined in operator for ' + this.id); + return; } - private loadOperatorData(availableOperators: any): any { - for (const operatorCRD of availableOperators) { - // console.log(operatorCRD.spec.names.kind, this.constructor.name) // debug CRD - if (operatorCRD.spec.names.kind === this.constructor.name) { - this.enabled = true; - this.version.installed = operatorCRD.spec.version - return operatorCRD; - } - } - return undefined; + for (const op of JSON.parse(operatorCRDList)) { + if (op.kind === this.constructor.name) { + //this.crd = op; + this.resourceDefinitions[op.kind] = op; + break; + } } - - private loadAdditionalResourceDefinitions() { - for (const [key, value] of Object.entries(this.additionalResourceDefinitions)) { - this.resourceDefinitions[key] = value; - } + } + + private loadOperatorData(availableOperators: any): any { + for (const operatorCRD of availableOperators) { + // console.log(operatorCRD.spec.names.kind, this.constructor.name) // debug CRD + if (operatorCRD.spec.names.kind === this.constructor.name) { + this.enabled = true; + this.version.installed = operatorCRD.spec.version; + return operatorCRD; + } + } + return undefined; + } + + private loadAdditionalResourceDefinitions() { + for (const [key, value] of Object.entries( + this.additionalResourceDefinitions, + )) { + this.resourceDefinitions[key] = value; } -} \ No newline at end of file + } +} diff --git a/server-refactored-v3/src/addons/plugins/postgresCluster.ts b/server-refactored-v3/src/addons/plugins/postgresCluster.ts index 8dee65d6..96c80eaf 100644 --- a/server-refactored-v3/src/addons/plugins/postgresCluster.ts +++ b/server-refactored-v3/src/addons/plugins/postgresCluster.ts @@ -1,190 +1,191 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class PostgresCluster extends Plugin implements IPlugin { - public id: string = 'postgresoperator';//same as operator name - public displayName = 'Crunchy Postgres Cluster' - public icon = '/img/addons/pgsql.svg' - public install: string = 'kubectl create -f https://operatorhub.io/install/v5/postgresql.yaml' - public url = 'https://artifacthub.io/packages/olm/community-operators/postgresql' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/postgresql' - public beta: boolean = true; + public id: string = 'postgresoperator'; //same as operator name + public displayName = 'Crunchy Postgres Cluster'; + public icon = '/img/addons/pgsql.svg'; + public install: string = + 'kubectl create -f https://operatorhub.io/install/v5/postgresql.yaml'; + public url = + 'https://artifacthub.io/packages/olm/community-operators/postgresql'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/postgresql'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'PostgresCluster.metadata.name':{ - type: 'text', - label: 'Redis Cluster Name', - name: 'metadata.name', - required: true, - default: 'pg-cluster', - description: 'The name of the Redis cluster' - }, - 'PostgresCluster.spec.postgresVersion':{ - type: 'number', - label: 'Postgres Version', - name: 'spec.postgresVersion', - default: 14, - required: true, - description: 'Version of the Running Postgresql' - }, - 'PostgresCluster.spec.instances[0].name':{ - type: 'text', - label: 'Cluster Name', - name: 'spec.instances[0].name', - default: 'instance-1', - required: true, - description: 'Name of the Instance' - }, - 'PostgresCluster.spec.instances[0].replicas':{ - type: 'number', - label: 'Clustersize', - name: 'spec.instances[0].replicas', - default: 1, - required: true, - description: 'Number of Postgres instances in the cluster' - }, - 'PostgresCluster.spec.instances[0].dataVolumeClaimSpec.resources.requests.storage':{ - type: 'text', - label: 'Data Volume size', - name: 'spec.instances[0].dataVolumeClaimSpec.resources.requests.storage', - default: '1Gi', - required: true, - description: 'Number of Postgres instances in the cluster' - }, - }; - - public env: any[] = [ + public formfields: { [key: string]: IPluginFormFields } = { + 'PostgresCluster.metadata.name': { + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'pg-cluster', + description: 'The name of the Redis cluster', + }, + 'PostgresCluster.spec.postgresVersion': { + type: 'number', + label: 'Postgres Version', + name: 'spec.postgresVersion', + default: 14, + required: true, + description: 'Version of the Running Postgresql', + }, + 'PostgresCluster.spec.instances[0].name': { + type: 'text', + label: 'Cluster Name', + name: 'spec.instances[0].name', + default: 'instance-1', + required: true, + description: 'Name of the Instance', + }, + 'PostgresCluster.spec.instances[0].replicas': { + type: 'number', + label: 'Clustersize', + name: 'spec.instances[0].replicas', + default: 1, + required: true, + description: 'Number of Postgres instances in the cluster', + }, + 'PostgresCluster.spec.instances[0].dataVolumeClaimSpec.resources.requests.storage': { - name: "DB_VENDOR", - value: "postgres" + type: 'text', + label: 'Data Volume size', + name: 'spec.instances[0].dataVolumeClaimSpec.resources.requests.storage', + default: '1Gi', + required: true, + description: 'Number of Postgres instances in the cluster', }, - { - name: "DB_ADDR", - valueFrom: { - secretKeyRef: { - name: "hippo-pguser-hippo", - key: "host" - } - } + }; + + public env: any[] = [ + { + name: 'DB_VENDOR', + value: 'postgres', + }, + { + name: 'DB_ADDR', + valueFrom: { + secretKeyRef: { + name: 'hippo-pguser-hippo', + key: 'host', + }, }, - { - name: "DB_PORT", - valueFrom: { - secretKeyRef: { - name: "hippo-pguser-hippo", - key: "port" - } - } + }, + { + name: 'DB_PORT', + valueFrom: { + secretKeyRef: { + name: 'hippo-pguser-hippo', + key: 'port', + }, }, - { - name: "DB_DATABASE", - valueFrom: { - secretKeyRef: { - name: "hippo-pguser-hippo", - key: "dbname" - } - } + }, + { + name: 'DB_DATABASE', + valueFrom: { + secretKeyRef: { + name: 'hippo-pguser-hippo', + key: 'dbname', + }, }, - { - name: "DB_USER", - valueFrom: { - secretKeyRef: { - name: "hippo-pguser-hippo", - key: "user" - } - } + }, + { + name: 'DB_USER', + valueFrom: { + secretKeyRef: { + name: 'hippo-pguser-hippo', + key: 'user', + }, }, - { - name: "DB_PASSWORD", - valueFrom: { - secretKeyRef: { - name: "hippo-pguser-hippo", - key: "password" - } - } - } - ] - - protected additionalResourceDefinitions: Object = { - // override default resource definitions since example is missing "backups" section - PostgresCluster : { - apiVersion: "postgres-operator.crunchydata.com/v1beta1", - kind: "PostgresCluster", - metadata: { - name: "hippo" + }, + { + name: 'DB_PASSWORD', + valueFrom: { + secretKeyRef: { + name: 'hippo-pguser-hippo', + key: 'password', }, - spec: { - image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-14.5-1", - postgresVersion: 14, - instances: [ - { - name: "instance1", - dataVolumeClaimSpec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "1Gi" - } - } - } - } - ], - backups: { - pgbackrest: { - image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.40-1", - repos: [ - { - name: "repo1", - volume: { - volumeClaimSpec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "1Gi" - } - } - } - } + }, + }, + ]; + + protected additionalResourceDefinitions: object = { + // override default resource definitions since example is missing "backups" section + PostgresCluster: { + apiVersion: 'postgres-operator.crunchydata.com/v1beta1', + kind: 'PostgresCluster', + metadata: { + name: 'hippo', + }, + spec: { + image: + 'registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-14.5-1', + postgresVersion: 14, + instances: [ + { + name: 'instance1', + dataVolumeClaimSpec: { + accessModes: ['ReadWriteOnce'], + resources: { + requests: { + storage: '1Gi', }, - { - name: "repo2", - volume: { - volumeClaimSpec: { - accessModes: [ - "ReadWriteOnce" - ], - resources: { - requests: { - storage: "1Gi" - } - } - } - } - } - ] - } + }, + }, }, - proxy: { - pgBouncer: { - image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.17-1" - } - } - } - } - } - - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } + ], + backups: { + pgbackrest: { + image: + 'registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.40-1', + repos: [ + { + name: 'repo1', + volume: { + volumeClaimSpec: { + accessModes: ['ReadWriteOnce'], + resources: { + requests: { + storage: '1Gi', + }, + }, + }, + }, + }, + { + name: 'repo2', + volume: { + volumeClaimSpec: { + accessModes: ['ReadWriteOnce'], + resources: { + requests: { + storage: '1Gi', + }, + }, + }, + }, + }, + ], + }, + }, + proxy: { + pgBouncer: { + image: + 'registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.17-1', + }, + }, + }, + }, + }; -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/redis.ts b/server-refactored-v3/src/addons/plugins/redis.ts index 56b778f9..a1c04f17 100644 --- a/server-refactored-v3/src/addons/plugins/redis.ts +++ b/server-refactored-v3/src/addons/plugins/redis.ts @@ -1,81 +1,84 @@ import { KubernetesObject } from '@kubernetes/client-node'; -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class Redis extends Plugin implements IPlugin { - public id: string = 'redis-operator';//same as operator name - public displayName = 'Opstree Redis' - public icon = '/img/addons/redis.svg' - public install: string = 'kubectl create -f https://operatorhub.io/install/stable/redis-operator.yaml' - public url = 'https://artifacthub.io/packages/olm/community-operators/redis-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/redis-operator' - public beta: boolean = true; + public id: string = 'redis-operator'; //same as operator name + public displayName = 'Opstree Redis'; + public icon = '/img/addons/redis.svg'; + public install: string = + 'kubectl create -f https://operatorhub.io/install/stable/redis-operator.yaml'; + public url = + 'https://artifacthub.io/packages/olm/community-operators/redis-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/redis-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'Redis.metadata.name':{ - type: 'text', - label: 'Redis Cluster Name', - name: 'metadata.name', - required: true, - default: 'redis-cluster', - description: 'The name of the Redis cluster' - }, - 'Redis.spec.redisExporter.enabled':{ - type: 'switch', - label: 'Exporter enabled', - name: 'spec.redisExporter.enabled', - default: true, - required: true - }, - 'Redis.spec.kubernetesConfig.resources.limits.cpu': { - type: 'text', - label: 'CPU Limit', - name: 'spec.kubernetesConfig.resources.limits.cpu', - default: '101m', - required: true - }, - 'Redis.spec.kubernetesConfig.resources.limits.memory': { - type: 'text', - label:'Memory Limit', - name: 'spec.kubernetesConfig.resources.limits.memory', - default: '128Mi', - required: true - }, - 'Redis.spec.kubernetesConfig.resources.requests.cpu': { - type: 'text', - label: 'CPU Requests', - name: 'spec.kubernetesConfig.resources.requests.cpu', - default: '101m', - required: true - }, - 'Redis.spec.kubernetesConfig.resources.requests.memory': { - type: 'text', - label: 'Memory Requests', - name: 'spec.kubernetesConfig.resources.requests.memory', - default: '128Mi', - required: true - }, - 'Redis.spec.storage.volumeClaimTemplate.spec.resources.requests.storage': { - type: 'text', - label: 'Storage Size', - name: 'spec.storage.volumeClaimTemplate.spec.resources.requests.storage', - default: '1Gi', - required: true - } - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'Redis.metadata.name': { + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'redis-cluster', + description: 'The name of the Redis cluster', + }, + 'Redis.spec.redisExporter.enabled': { + type: 'switch', + label: 'Exporter enabled', + name: 'spec.redisExporter.enabled', + default: true, + required: true, + }, + 'Redis.spec.kubernetesConfig.resources.limits.cpu': { + type: 'text', + label: 'CPU Limit', + name: 'spec.kubernetesConfig.resources.limits.cpu', + default: '101m', + required: true, + }, + 'Redis.spec.kubernetesConfig.resources.limits.memory': { + type: 'text', + label: 'Memory Limit', + name: 'spec.kubernetesConfig.resources.limits.memory', + default: '128Mi', + required: true, + }, + 'Redis.spec.kubernetesConfig.resources.requests.cpu': { + type: 'text', + label: 'CPU Requests', + name: 'spec.kubernetesConfig.resources.requests.cpu', + default: '101m', + required: true, + }, + 'Redis.spec.kubernetesConfig.resources.requests.memory': { + type: 'text', + label: 'Memory Requests', + name: 'spec.kubernetesConfig.resources.requests.memory', + default: '128Mi', + required: true, + }, + 'Redis.spec.storage.volumeClaimTemplate.spec.resources.requests.storage': { + type: 'text', + label: 'Storage Size', + name: 'spec.storage.volumeClaimTemplate.spec.resources.requests.storage', + default: '1Gi', + required: true, + }, + }; - public env: any[] = [] + public env: any[] = []; - protected additionalResourceDefinitions: Object = {} + protected additionalResourceDefinitions: object = {}; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/addons/plugins/redisCluster.ts b/server-refactored-v3/src/addons/plugins/redisCluster.ts index 5e042b14..4dfc656c 100644 --- a/server-refactored-v3/src/addons/plugins/redisCluster.ts +++ b/server-refactored-v3/src/addons/plugins/redisCluster.ts @@ -1,86 +1,90 @@ -import {Plugin, IPlugin, IPluginFormFields} from './plugin'; +import { Plugin, IPlugin, IPluginFormFields } from './plugin'; // Classname must be same as the CRD's Name export class RedisCluster extends Plugin implements IPlugin { - public id: string = 'redis-operator';//same as operator name - public displayName = 'Opstree Redis Cluster' - public icon = '/img/addons/redis.svg' - public install: string = 'kubectl create -f https://operatorhub.io/install/stable/redis-operator.yaml' - public url = 'https://artifacthub.io/packages/olm/community-operators/redis-operator' - public docs = [ - { - title: 'Kubero Docs', url: '' - } - ] - public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/community-operators/redis-operator' - public beta: boolean = true; + public id: string = 'redis-operator'; //same as operator name + public displayName = 'Opstree Redis Cluster'; + public icon = '/img/addons/redis.svg'; + public install: string = + 'kubectl create -f https://operatorhub.io/install/stable/redis-operator.yaml'; + public url = + 'https://artifacthub.io/packages/olm/community-operators/redis-operator'; + public docs = [ + { + title: 'Kubero Docs', + url: '', + }, + ]; + public artifact_url = + 'https://artifacthub.io/api/v1/packages/olm/community-operators/redis-operator'; + public beta: boolean = true; - public formfields: {[key: string]: IPluginFormFields} = { - 'RedisCluster.metadata.name':{ - type: 'text', - label: 'Redis Cluster Name', - name: 'metadata.name', - required: true, - default: 'redis-cluster', - description: 'The name of the Redis cluster' - }, - 'RedisCluster.spec.clusterSize':{ - type: 'number', - label: 'Clustersize', - name: 'spec.clusterSize', - default: 3, - required: true, - description: 'Number of Redis nodes in the cluster' - }, - 'RedisCluster.spec.redisExporter.enabled':{ - type: 'switch', - label: 'Exporter enabled', - name: 'spec.redisExporter.enabled', - default: true, - required: true - }, - 'RedisCluster.spec.kubernetesConfig.resources.limits.cpu': { - type: 'text', - label: 'CPU Limit', - name: 'spec.kubernetesConfig.resources.limits.cpu', - default: '101m', - required: true - }, - 'RedisCluster.spec.kubernetesConfig.resources.limits.memory': { - type: 'text', - label:'Memory Limit', - name: 'spec.kubernetesConfig.resources.limits.memory', - default: '128Mi', - required: true - }, - 'RedisCluster.spec.kubernetesConfig.resources.requests.cpu': { - type: 'text', - label: 'CPU Requests', - name: 'spec.kubernetesConfig.resources.requests.cpu', - default: '101m', - required: true - }, - 'RedisCluster.spec.kubernetesConfig.resources.requests.memory': { - type: 'text', - label: 'Memory Requests', - name: 'spec.kubernetesConfig.resources.requests.memory', - default: '128Mi', - required: true - }, - 'RedisCluster.spec.storage.volumeClaimTemplate.spec.resources.requests.storage': { - type: 'text', - label: 'Storage Size', - name: 'spec.storage.volumeClaimTemplate.spec.resources.requests.storage', - default: '1Gi', - required: true - } - }; + public formfields: { [key: string]: IPluginFormFields } = { + 'RedisCluster.metadata.name': { + type: 'text', + label: 'Redis Cluster Name', + name: 'metadata.name', + required: true, + default: 'redis-cluster', + description: 'The name of the Redis cluster', + }, + 'RedisCluster.spec.clusterSize': { + type: 'number', + label: 'Clustersize', + name: 'spec.clusterSize', + default: 3, + required: true, + description: 'Number of Redis nodes in the cluster', + }, + 'RedisCluster.spec.redisExporter.enabled': { + type: 'switch', + label: 'Exporter enabled', + name: 'spec.redisExporter.enabled', + default: true, + required: true, + }, + 'RedisCluster.spec.kubernetesConfig.resources.limits.cpu': { + type: 'text', + label: 'CPU Limit', + name: 'spec.kubernetesConfig.resources.limits.cpu', + default: '101m', + required: true, + }, + 'RedisCluster.spec.kubernetesConfig.resources.limits.memory': { + type: 'text', + label: 'Memory Limit', + name: 'spec.kubernetesConfig.resources.limits.memory', + default: '128Mi', + required: true, + }, + 'RedisCluster.spec.kubernetesConfig.resources.requests.cpu': { + type: 'text', + label: 'CPU Requests', + name: 'spec.kubernetesConfig.resources.requests.cpu', + default: '101m', + required: true, + }, + 'RedisCluster.spec.kubernetesConfig.resources.requests.memory': { + type: 'text', + label: 'Memory Requests', + name: 'spec.kubernetesConfig.resources.requests.memory', + default: '128Mi', + required: true, + }, + 'RedisCluster.spec.storage.volumeClaimTemplate.spec.resources.requests.storage': + { + type: 'text', + label: 'Storage Size', + name: 'spec.storage.volumeClaimTemplate.spec.resources.requests.storage', + default: '1Gi', + required: true, + }, + }; - public env: any[] = [] + public env: any[] = []; - constructor(availableOperators: any) { - super(); - super.init(availableOperators); - } - -} \ No newline at end of file + constructor(availableOperators: any) { + super(); + super.init(availableOperators); + } +} diff --git a/server-refactored-v3/src/app.controller.spec.ts b/server-refactored-v3/src/app.controller.spec.ts index c86814d2..a22ca32c 100644 --- a/server-refactored-v3/src/app.controller.spec.ts +++ b/server-refactored-v3/src/app.controller.spec.ts @@ -13,5 +13,4 @@ describe('AppController', () => { appController = app.get(AppController); }); - }); diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts index 51cc1471..46d62873 100644 --- a/server-refactored-v3/src/app.controller.ts +++ b/server-refactored-v3/src/app.controller.ts @@ -1,4 +1,14 @@ -import { Controller, Request, All, Get, Post, UseGuards, HttpStatus, HttpCode, Res } from '@nestjs/common'; +import { + Controller, + Request, + All, + Get, + Post, + UseGuards, + HttpStatus, + HttpCode, + Res, +} from '@nestjs/common'; //import { Response } from 'express'; import { AppService } from './app.service'; import { AuthGuard } from '@nestjs/passport'; diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 7109ebb5..2405cd9b 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -19,7 +19,6 @@ import { AddonsModule } from './addons/addons.module'; import { NotificationsModule } from './notifications/notifications.module'; import { SecurityModule } from './security/security.module'; - @Module({ imports: [ ServeStaticModule.forRoot({ diff --git a/server-refactored-v3/src/app.service.ts b/server-refactored-v3/src/app.service.ts index a031ef59..7263d33a 100644 --- a/server-refactored-v3/src/app.service.ts +++ b/server-refactored-v3/src/app.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; @Injectable() -export class AppService { -} +export class AppService {} diff --git a/server-refactored-v3/src/apps/app/app.ts b/server-refactored-v3/src/apps/app/app.ts index 996c5fa9..f263baae 100644 --- a/server-refactored-v3/src/apps/app/app.ts +++ b/server-refactored-v3/src/apps/app/app.ts @@ -1,265 +1,276 @@ -import { +import { IApp, IGithubRepository, ICronjob, IExtraVolume, } from '../apps.interface'; -import { IKubectlMetadata, IKubectlApp } from "../../kubernetes/kubernetes.interface"; +import { + IKubectlMetadata, + IKubectlApp, +} from '../../kubernetes/kubernetes.interface'; import { IAddon } from '../../addons/addons.interface'; -import { ISecurityContext, IPodSize } from "../../settings/settings.interface" +import { ISecurityContext, IPodSize } from '../../settings/settings.interface'; import { hashSync, genSaltSync } from 'bcrypt'; import { Buildpack } from '../../settings/buildpack/buildpack'; -export class KubectlApp implements IKubectlApp{ - apiVersion: string; - kind: string; - metadata: IKubectlMetadata; - spec: App; - status: { conditions: [Array: Object]; deployedRelease?: { name: string; manifest: string; }; }; +export class KubectlApp implements IKubectlApp { + apiVersion: string; + kind: string; + metadata: IKubectlMetadata; + spec: App; + status: { + conditions: [Array: object]; + deployedRelease?: { name: string; manifest: string }; + }; - constructor(app: App) { - this.apiVersion = "application.kubero.dev/v1alpha1"; - this.kind = "KuberoApp"; - this.metadata = { - name: app.name, - labels: { - manager: 'kubero', - } - } - this.spec = app; - } -} - -export class App implements IApp{ - public name: string - public pipeline: string - public phase: string - public sleep: string - public buildpack: string - public deploymentstrategy: 'git' | 'docker'; - public buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; - public gitrepo?: IGithubRepository - public branch: string - public autodeploy: boolean - public podsize: IPodSize - public autoscale: boolean - //public envVars: {[key: string]: string} = {} - public basicAuth: { - enabled: boolean; - realm: string; - accounts: { - user: string; - pass: string; - hash?: string; - }[]; + constructor(app: App) { + this.apiVersion = 'application.kubero.dev/v1alpha1'; + this.kind = 'KuberoApp'; + this.metadata = { + name: app.name, + labels: { + manager: 'kubero', + }, }; - public envVars: {}[] = [] - public extraVolumes: IExtraVolume[] = [] - public cronjobs: ICronjob[] = [] - public addons: IAddon[] = [] - - public web: { - replicaCount: number - autoscaling: { - minReplicas: number - maxReplicas: number - targetCPUUtilizationPercentage?: number - targetMemoryUtilizationPercentage?: number - } - } + this.spec = app; + } +} - public worker: { - replicaCount: number - autoscaling: { - minReplicas: number - maxReplicas: number - targetCPUUtilizationPercentage?: number - targetMemoryUtilizationPercentage?: number - } - } +export class App implements IApp { + public name: string; + public pipeline: string; + public phase: string; + public sleep: string; + public buildpack: string; + public deploymentstrategy: 'git' | 'docker'; + public buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; + public gitrepo?: IGithubRepository; + public branch: string; + public autodeploy: boolean; + public podsize: IPodSize; + public autoscale: boolean; + //public envVars: {[key: string]: string} = {} + public basicAuth: { + enabled: boolean; + realm: string; + accounts: { + user: string; + pass: string; + hash?: string; + }[]; + }; + public envVars: {}[] = []; + public extraVolumes: IExtraVolume[] = []; + public cronjobs: ICronjob[] = []; + public addons: IAddon[] = []; - private affinity: {}; - private autoscaling: { - enabled: boolean, + public web: { + replicaCount: number; + autoscaling: { + minReplicas: number; + maxReplicas: number; + targetCPUUtilizationPercentage?: number; + targetMemoryUtilizationPercentage?: number; }; - private fullnameOverride: ""; + }; - public image: { - containerPort: number, - pullPolicy: 'Always', - repository: string, - tag: string, - command: [string], - fetch: { - repository: string, - tag: string, - securityContext?: ISecurityContext - } - build: { - repository: string, - tag: string, - securityContext?: ISecurityContext - } - run: { - repository: string, - tag: string, - readOnlyAppStorage?: boolean, - securityContext: ISecurityContext - } + public worker: { + replicaCount: number; + autoscaling: { + minReplicas: number; + maxReplicas: number; + targetCPUUtilizationPercentage?: number; + targetMemoryUtilizationPercentage?: number; }; + }; - public vulnerabilityscan: { - enabled: boolean - schedule: string - image: { - repository: string - tag: string - } - } + private affinity: {}; + private autoscaling: { + enabled: boolean; + }; + private fullnameOverride: ''; - private imagePullSecrets: []; - public ingress: { - annotations: Object, - className: string, - enabled: boolean, - hosts: [ - { - host: string, - paths: [ - {path: string, pathType: string} - ] - } - ], - tls: [ - { - hosts: string[], - secretName: string - } - ] | [] + public image: { + containerPort: number; + pullPolicy: 'Always'; + repository: string; + tag: string; + command: [string]; + fetch: { + repository: string; + tag: string; + securityContext?: ISecurityContext; }; - private nameOverride: ""; - private nodeSelector: {}; - private podAnnotations: {}; - private podSecurityContext: {}; - private replicaCount: 1; - public resources: {}; - private service: { - port: 80, - type: 'ClusterIP' + build: { + repository: string; + tag: string; + securityContext?: ISecurityContext; }; - public serviceAccount: { - annotations: Object, - create: boolean, - name: string, + run: { + repository: string; + tag: string; + readOnlyAppStorage?: boolean; + securityContext: ISecurityContext; }; - private tolerations: []; + }; - public healthcheck: { - enabled: boolean, - path: string, - startupSeconds: number, - timeoutSeconds: number, - periodSeconds: number, + public vulnerabilityscan: { + enabled: boolean; + schedule: string; + image: { + repository: string; + tag: string; }; + }; + + private imagePullSecrets: []; + public ingress: { + annotations: object; + className: string; + enabled: boolean; + hosts: [ + { + host: string; + paths: [{ path: string; pathType: string }]; + }, + ]; + tls: + | [ + { + hosts: string[]; + secretName: string; + }, + ] + | []; + }; + private nameOverride: ''; + private nodeSelector: {}; + private podAnnotations: {}; + private podSecurityContext: {}; + private replicaCount: 1; + public resources: {}; + private service: { + port: 80; + type: 'ClusterIP'; + }; + public serviceAccount: { + annotations: object; + create: boolean; + name: string; + }; + private tolerations: []; - constructor( - app: IApp - ) { - this.name = app.name - this.pipeline = app.pipeline - this.phase = app.phase - this.sleep = app.sleep - this.buildpack = app.buildpack - this.deploymentstrategy = app.deploymentstrategy - this.buildstrategy = app.buildstrategy - this.gitrepo = app.gitrepo - this.branch = app.branch - this.autodeploy = app.autodeploy - this.podsize = app.podsize - this.autoscale = app.autoscale // TODO: may be redundant with autoscaling.enabled + public healthcheck: { + enabled: boolean; + path: string; + startupSeconds: number; + timeoutSeconds: number; + periodSeconds: number; + }; - const salt = genSaltSync(10); - if (app.basicAuth !== undefined) { - this.basicAuth = { - realm: app.basicAuth.realm, - enabled: app.basicAuth.enabled, - accounts: app.basicAuth.accounts.map(account => { - return { - user: account.user, - pass: account.pass, - // generate hash with bcrypt from user and pass - //hash: account.user+':$5$'+crypto.createHash('sha256').update(account.user+account.pass).digest('base64') - //hash: account.user+':{SHA}'+crypto.createHash('sha1').update(account.pass).digest('base64') // works - hash: account.user+':'+hashSync(account.pass, salt) - } - }) - } - } else { - this.basicAuth = { - realm: 'Authenticate', - enabled: false, - accounts: [] - } - } + constructor(app: IApp) { + this.name = app.name; + this.pipeline = app.pipeline; + this.phase = app.phase; + this.sleep = app.sleep; + this.buildpack = app.buildpack; + this.deploymentstrategy = app.deploymentstrategy; + this.buildstrategy = app.buildstrategy; + this.gitrepo = app.gitrepo; + this.branch = app.branch; + this.autodeploy = app.autodeploy; + this.podsize = app.podsize; + this.autoscale = app.autoscale; // TODO: may be redundant with autoscaling.enabled - this.envVars = app.envVars + const salt = genSaltSync(10); + if (app.basicAuth !== undefined) { + this.basicAuth = { + realm: app.basicAuth.realm, + enabled: app.basicAuth.enabled, + accounts: app.basicAuth.accounts.map((account) => { + return { + user: account.user, + pass: account.pass, + // generate hash with bcrypt from user and pass + //hash: account.user+':$5$'+crypto.createHash('sha256').update(account.user+account.pass).digest('base64') + //hash: account.user+':{SHA}'+crypto.createHash('sha1').update(account.pass).digest('base64') // works + hash: account.user + ':' + hashSync(account.pass, salt), + }; + }), + }; + } else { + this.basicAuth = { + realm: 'Authenticate', + enabled: false, + accounts: [], + }; + } - this.serviceAccount = app.serviceAccount; + this.envVars = app.envVars; - this.extraVolumes = app.extraVolumes + this.serviceAccount = app.serviceAccount; - this.cronjobs = app.cronjobs + this.extraVolumes = app.extraVolumes; - this.addons = app.addons + this.cronjobs = app.cronjobs; - this.web = app.web - this.worker = app.worker + this.addons = app.addons; - this.affinity = {}; - this.autoscaling = { - enabled: app.autoscale - } - this.fullnameOverride = "", + this.web = app.web; + this.worker = app.worker; - this.image = { - containerPort: app.image.containerPort, - pullPolicy: 'Always', - repository: app.image.repository || 'ghcr.io/kubero-dev/idler', - tag: app.image.tag || 'v1', - command: app.image.command, - fetch: app.image.fetch, - build: app.image.build, - run: app.image.run, - } + this.affinity = {}; + this.autoscaling = { + enabled: app.autoscale, + }; + (this.fullnameOverride = ''), + (this.image = { + containerPort: app.image.containerPort, + pullPolicy: 'Always', + repository: app.image.repository || 'ghcr.io/kubero-dev/idler', + tag: app.image.tag || 'v1', + command: app.image.command, + fetch: app.image.fetch, + build: app.image.build, + run: app.image.run, + }); - // function to set security context, required for backwards compatibility - // Added in v1.11.0 - this.image.fetch.securityContext = Buildpack.SetSecurityContext(this.image.fetch.securityContext) - this.image.build.securityContext = Buildpack.SetSecurityContext(this.image.build.securityContext) - this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + this.image.fetch.securityContext = Buildpack.SetSecurityContext( + this.image.fetch.securityContext, + ); + this.image.build.securityContext = Buildpack.SetSecurityContext( + this.image.build.securityContext, + ); + this.image.run.securityContext = Buildpack.SetSecurityContext( + this.image.run.securityContext, + ); - this.vulnerabilityscan = app.vulnerabilityscan + this.vulnerabilityscan = app.vulnerabilityscan; - this.imagePullSecrets = [] + this.imagePullSecrets = []; - this.ingress = app.ingress - this.ingress.className = app.ingress.className || process.env.KUBERNETES_INGRESS_CLASSNAME || "nginx" - this.ingress.enabled = true + this.ingress = app.ingress; + this.ingress.className = + app.ingress.className || + process.env.KUBERNETES_INGRESS_CLASSNAME || + 'nginx'; + this.ingress.enabled = true; - this.nameOverride= "", - this.nodeSelector= {}, - this.podAnnotations= {}, - this.podSecurityContext= {}, - this.replicaCount= 1, - this.resources= app.podsize.resources, - this.service= { - port: 80, - type: 'ClusterIP' - }, - this.tolerations= [] + (this.nameOverride = ''), + (this.nodeSelector = {}), + (this.podAnnotations = {}), + (this.podSecurityContext = {}), + (this.replicaCount = 1), + (this.resources = app.podsize.resources), + (this.service = { + port: 80, + type: 'ClusterIP', + }), + (this.tolerations = []); - this.healthcheck = app.healthcheck - } + this.healthcheck = app.healthcheck; + } } - diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index c8ecc93a..308b1703 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -1,4 +1,17 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Logger, Param, Post, Put, Res } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpException, + HttpStatus, + Logger, + Param, + Post, + Put, + Res, +} from '@nestjs/common'; import { Response } from 'express'; import { AppsService } from './apps.service'; import { IUser } from '../auth/auth.interface'; @@ -6,9 +19,7 @@ import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/apps', version: '1' }) export class AppsController { - constructor( - private readonly appsService: AppsService, - ) {} + constructor(private readonly appsService: AppsService) {} @ApiOperation({ summary: 'Get app informations from a specific app' }) @Get('/:pipeline/:phase/:app') @@ -29,7 +40,6 @@ export class AppsController { @Param('app') appName: string, @Body() app: any, ) { - if (appName !== 'new') { const msg = 'App name does not match the URL'; Logger.error(msg); @@ -51,7 +61,7 @@ export class AppsController { id: 1, method: 'local', username: 'admin', - apitoken: '1234567890' + apitoken: '1234567890', }; return this.appsService.createApp(app, user); } @@ -65,9 +75,9 @@ export class AppsController { @Param('resourceVersion') resourceVersion: string, @Body() app: any, ) { - if (appName !== app.name) { - const msg = 'App name does not match the URL '+appName+' != '+app.name; + const msg = + 'App name does not match the URL ' + appName + ' != ' + app.name; Logger.error(msg); throw new HttpException(msg, HttpStatus.BAD_REQUEST); } @@ -77,7 +87,7 @@ export class AppsController { id: 1, method: 'local', username: 'admin', - apitoken: '1234567890' + apitoken: '1234567890', }; return this.appsService.updateApp(app, resourceVersion, user); } @@ -94,16 +104,14 @@ export class AppsController { id: 1, method: 'local', username: 'admin', - apitoken: '1234567890' + apitoken: '1234567890', }; return this.appsService.deleteApp(pipeline, phase, app, user); } @ApiOperation({ summary: 'Start a Pull Request App' }) @Post('/pullrequest') - async startPullRequest( - @Body() body: any, - ) { + async startPullRequest(@Body() body: any) { return this.appsService.createPRApp( body.branch, body.branch, @@ -134,10 +142,9 @@ export class AppsController { id: 1, method: 'local', username: 'admin', - apitoken: '1234567890' + apitoken: '1234567890', }; return this.appsService.restartApp(pipeline, phase, app, user); } - } diff --git a/server-refactored-v3/src/apps/apps.interface.ts b/server-refactored-v3/src/apps/apps.interface.ts index c2b71496..2a3347b0 100644 --- a/server-refactored-v3/src/apps/apps.interface.ts +++ b/server-refactored-v3/src/apps/apps.interface.ts @@ -1,106 +1,106 @@ -import { IAddon } from "../addons/addons.interface" -import { IPodSize, ISecurityContext } from "../settings/settings.interface" -import { IKubectlMetadata } from "../kubernetes/kubernetes.interface" +import { IAddon } from '../addons/addons.interface'; +import { IPodSize, ISecurityContext } from '../settings/settings.interface'; +import { IKubectlMetadata } from '../kubernetes/kubernetes.interface'; export interface IApp { - name: string, - pipeline: string, - phase: string, - sleep: string, - buildpack: string, - deploymentstrategy: 'git' | 'docker', - buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks', - gitrepo?: IGithubRepository, - branch: string, - autodeploy: boolean, - podsize: IPodSize, - autoscale: boolean, + name: string; + pipeline: string; + phase: string; + sleep: string; + buildpack: string; + deploymentstrategy: 'git' | 'docker'; + buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; + gitrepo?: IGithubRepository; + branch: string; + autodeploy: boolean; + podsize: IPodSize; + autoscale: boolean; basicAuth: { - enabled: boolean, - realm: string, - accounts: { - user: string, - pass: string, - hash?: string, - }[] - }, - envVars: {}[], - image : { - repository: string, - tag: string, - command: [string], - pullPolicy: 'Always', - containerPort: number, - fetch: { - repository: string, - tag: string, - securityContext?: ISecurityContext - } - build: { - repository: string, - tag: string, - securityContext?: ISecurityContext - } - run: { - repository: string, - readOnlyAppStorage?: boolean, - tag: string, - readOnly?: boolean, - securityContext: ISecurityContext - } - } + enabled: boolean; + realm: string; + accounts: { + user: string; + pass: string; + hash?: string; + }[]; + }; + envVars: {}[]; + image: { + repository: string; + tag: string; + command: [string]; + pullPolicy: 'Always'; + containerPort: number; + fetch: { + repository: string; + tag: string; + securityContext?: ISecurityContext; + }; + build: { + repository: string; + tag: string; + securityContext?: ISecurityContext; + }; + run: { + repository: string; + readOnlyAppStorage?: boolean; + tag: string; + readOnly?: boolean; + securityContext: ISecurityContext; + }; + }; web: { - replicaCount: number - autoscaling: { - minReplicas: number - maxReplicas: number - targetCPUUtilizationPercentage?: number - targetMemoryUtilizationPercentage?: number - } - } + replicaCount: number; + autoscaling: { + minReplicas: number; + maxReplicas: number; + targetCPUUtilizationPercentage?: number; + targetMemoryUtilizationPercentage?: number; + }; + }; worker: { - replicaCount: number - autoscaling: { - minReplicas: number - maxReplicas: number - targetCPUUtilizationPercentage?: number - targetMemoryUtilizationPercentage?: number - } - } + replicaCount: number; + autoscaling: { + minReplicas: number; + maxReplicas: number; + targetCPUUtilizationPercentage?: number; + targetMemoryUtilizationPercentage?: number; + }; + }; - extraVolumes: IExtraVolume[], - cronjobs: ICronjob[] - addons: IAddon[] + extraVolumes: IExtraVolume[]; + cronjobs: ICronjob[]; + addons: IAddon[]; vulnerabilityscan: { - enabled: boolean - schedule: string - image: { - repository: string - tag: string - } - } + enabled: boolean; + schedule: string; + image: { + repository: string; + tag: string; + }; + }; ingress: { - annotations: Object, - className: string, - enabled: boolean, - hosts: [ - { - host: string - paths: [ - {path: string, pathType: string} - ] - } - ], - tls: [ + annotations: object; + className: string; + enabled: boolean; + hosts: [ + { + host: string; + paths: [{ path: string; pathType: string }]; + }, + ]; + tls: + | [ { - hosts: string[], - secretName: string - } - ] | [] - }, -/* + hosts: string[]; + secretName: string; + }, + ] + | []; + }; + /* affinity: {}, fullnameOverride: string, imagePullSecrets: [], @@ -122,53 +122,53 @@ export interface IApp { podSecurityContext: {}, replicaCount: number, */ - resources: {}, -/* + resources: {}; + /* service: { port: number, type: string }, */ serviceAccount: { - annotations: {}, - create: boolean, - name: string, - }, + annotations: {}; + create: boolean; + name: string; + }; //tolerations: [], healthcheck: { - enabled: boolean, - path: string, - startupSeconds: number, - timeoutSeconds: number, - periodSeconds: number, - }, + enabled: boolean; + path: string; + startupSeconds: number; + timeoutSeconds: number; + periodSeconds: number; + }; } export interface IExtraVolume { - name: string, - mountPath: string, - emptyDir: boolean, - size: string, - storageClass: string, - accessModes: string[], + name: string; + mountPath: string; + emptyDir: boolean; + size: string; + storageClass: string; + accessModes: string[]; } export interface IGithubRepository { - admin: boolean, - description?: string, - id?: number, - name?: string, - node_id?: string, - owner?: string, - private?: boolean, - ssh_url?: string - clone_url?: string, + admin: boolean; + description?: string; + id?: number; + name?: string; + node_id?: string; + owner?: string; + private?: boolean; + ssh_url?: string; + clone_url?: string; } export interface ICronjob { - name: string, - schedule: string, - command: [string], - image: string, - imagePullPolicy: string, -} \ No newline at end of file + name: string; + schedule: string; + command: [string]; + image: string; + imagePullPolicy: string; +} diff --git a/server-refactored-v3/src/apps/apps.service.spec.ts b/server-refactored-v3/src/apps/apps.service.spec.ts index ff4c0a34..f88ca5ac 100644 --- a/server-refactored-v3/src/apps/apps.service.spec.ts +++ b/server-refactored-v3/src/apps/apps.service.spec.ts @@ -49,8 +49,16 @@ describe('AppsService', () => { const result = await service.getApp(pipelineName, phaseName, appName); - expect(pipelinesService.getContext).toHaveBeenCalledWith(pipelineName, phaseName); - expect(kubernetesService.getApp).toHaveBeenCalledWith(pipelineName, phaseName, appName, contextName); + expect(pipelinesService.getContext).toHaveBeenCalledWith( + pipelineName, + phaseName, + ); + expect(kubernetesService.getApp).toHaveBeenCalledWith( + pipelineName, + phaseName, + appName, + contextName, + ); expect(result).toBe(app); }); @@ -59,11 +67,16 @@ describe('AppsService', () => { const phaseName = 'test-phase'; const appName = 'test-app'; - jest.spyOn(pipelinesService, 'getContext').mockResolvedValue('example-context'); + jest + .spyOn(pipelinesService, 'getContext') + .mockResolvedValue('example-context'); const result = await service.getApp(pipelineName, phaseName, appName); - expect(pipelinesService.getContext).toHaveBeenCalledWith(pipelineName, phaseName); + expect(pipelinesService.getContext).toHaveBeenCalledWith( + pipelineName, + phaseName, + ); expect(kubernetesService.getApp).not.toHaveBeenCalled(); expect(result).toBeUndefined(); }); diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index d66af5e4..96725eca 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -12,10 +12,8 @@ import { SettingsService } from 'src/settings/settings.service'; //import YAML from 'yaml'; import { KubectlTemplate } from 'src/templates/template'; - @Injectable() export class AppsService { - private logger = new Logger(AppsService.name); private YAML = require('yaml'); @@ -23,355 +21,505 @@ export class AppsService { private kubectl: KubernetesService, private pipelinesService: PipelinesService, private NotificationsService: NotificationsService, - private settingsService: SettingsService - ) {} - - public async getApp(pipelineName: string, phaseName: string, appName: string) { - this.logger.debug('get App: '+appName+' in '+ pipelineName+' phase: '+phaseName); - const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); - + private settingsService: SettingsService, + ) { + this.logger.log('AppsService initialized'); + } + + public async getApp( + pipelineName: string, + phaseName: string, + appName: string, + ) { + this.logger.debug( + 'get App: ' + appName + ' in ' + pipelineName + ' phase: ' + phaseName, + ); + const contextName = await this.pipelinesService.getContext( + pipelineName, + phaseName, + ); + if (contextName) { - try { - let app = await this.kubectl.getApp(pipelineName, phaseName, appName, contextName); - app.metadata.managedFields = [{}]; - app.status.deployedRelease = undefined; - return app; - } catch (error) { - this.logger.error('getApp error: '+error); - return null; - } + try { + const app = await this.kubectl.getApp(pipelineName, phaseName, appName, contextName); + app.metadata.managedFields = [{}]; + app.status.deployedRelease = undefined; + return app; + } catch (error) { + this.logger.error('getApp error: ' + error); + return null; + } } } public async createApp(app: App, user: IUser) { - this.logger.debug('create App: '+app.name+' in '+ app.pipeline+' phase: '+app.phase + ' deploymentstrategy: '+app.deploymentstrategy); - - if ( process.env.KUBERO_READONLY == 'true'){ - this.logger.log('KUBERO_READONLY is set to true, not creating app ' + app.name); - return; + this.logger.debug( + 'create App: ' + + app.name + + ' in ' + + app.pipeline + + ' phase: ' + + app.phase + + ' deploymentstrategy: ' + + app.deploymentstrategy, + ); + + if (process.env.KUBERO_READONLY == 'true') { + this.logger.log( + 'KUBERO_READONLY is set to true, not creating app ' + app.name, + ); + return; } - const contextName = await this.pipelinesService.getContext(app.pipeline, app.phase); + const contextName = await this.pipelinesService.getContext( + app.pipeline, + app.phase, + ); if (contextName) { - await this.kubectl.createApp(app, contextName); - - if (app.deploymentstrategy == 'git' && (app.buildstrategy == 'dockerfile' || app.buildstrategy == 'nixpacks' || app.buildstrategy == 'buildpacks')){ - this.triggerImageBuild(app.pipeline, app.phase, app.name); - } - //this.appStateList.push(app); - - const m = { - 'name': 'newApp', - 'user': user.username, - 'resource': 'app', - 'action': 'create', - 'severity': 'normal', - 'message': 'Created new app: '+app.name+' in '+ app.pipeline+' phase: '+app.phase, - 'pipelineName':app.pipeline, - 'phaseName': app.phase, - 'appName': app.name, - 'data': { - 'app': app - } - } as INotification; - this.NotificationsService.send(m); + await this.kubectl.createApp(app, contextName); + + if ( + app.deploymentstrategy == 'git' && + (app.buildstrategy == 'dockerfile' || + app.buildstrategy == 'nixpacks' || + app.buildstrategy == 'buildpacks') + ) { + this.triggerImageBuild(app.pipeline, app.phase, app.name); + } + //this.appStateList.push(app); + + const m = { + name: 'newApp', + user: user.username, + resource: 'app', + action: 'create', + severity: 'normal', + message: + 'Created new app: ' + + app.name + + ' in ' + + app.pipeline + + ' phase: ' + + app.phase, + pipelineName: app.pipeline, + phaseName: app.phase, + appName: app.name, + data: { + app: app, + }, + } as INotification; + this.NotificationsService.send(m); } - } - public async triggerImageBuild(pipeline: string, phase: string, appName: string) { + public async triggerImageBuild( + pipeline: string, + phase: string, + appName: string, + ) { const contextName = await this.pipelinesService.getContext(pipeline, phase); - const namespace = pipeline+'-'+phase; - - const appresult = await this.getApp(pipeline, phase, appName) + const namespace = pipeline + '-' + phase; + const appresult = await this.getApp(pipeline, phase, appName); const app = appresult as IKubectlApp; let repo = ''; if (app.spec.gitrepo?.admin) { - repo = app.spec.gitrepo.ssh_url || ""; + repo = app.spec.gitrepo.ssh_url || ''; } else { - repo = app.spec.gitrepo?.clone_url || ""; + repo = app.spec.gitrepo?.clone_url || ''; } let dockerfilePath = 'Dockerfile'; if (app.spec.buildstrategy === 'dockerfile') { - //dockerfilePath = app.spec.dockerfile || 'Dockerfile'; + //dockerfilePath = app.spec.dockerfile || 'Dockerfile'; } else if (app.spec.buildstrategy === 'nixpacks') { - dockerfilePath = '.nixpacks/Dockerfile'; + dockerfilePath = '.nixpacks/Dockerfile'; } - const timestamp = new Date().getTime(); if (contextName) { - this.kubectl.setCurrentContext(contextName); - - this.kubectl.createBuildJob( - namespace, - appName, - pipeline, - app.spec.buildstrategy, - dockerfilePath, - { - url: repo, - ref: app.spec.branch, //git commit reference - }, - { - image: `${process.env.KUBERO_BUILD_REGISTRY}/${pipeline}/${appName}`, - tag: app.spec.branch+"-"+timestamp - } - ); + this.kubectl.setCurrentContext(contextName); + + this.kubectl.createBuildJob( + namespace, + appName, + pipeline, + app.spec.buildstrategy, + dockerfilePath, + { + url: repo, + ref: app.spec.branch, //git commit reference + }, + { + image: `${process.env.KUBERO_BUILD_REGISTRY}/${pipeline}/${appName}`, + tag: app.spec.branch + '-' + timestamp, + }, + ); } return { - status: 'ok', - message: 'build started', - deploymentstrategy: app?.spec?.deploymentstrategy, - pipeline: pipeline, - phase: phase, - app: appName + status: 'ok', + message: 'build started', + deploymentstrategy: app?.spec?.deploymentstrategy, + pipeline: pipeline, + phase: phase, + app: appName, }; } // delete a app in a pipeline and phase - public async deleteApp(pipelineName: string, phaseName: string, appName: string, user: IUser) { - this.logger.debug('delete App: '+appName+' in '+ pipelineName+' phase: '+phaseName); - - if ( process.env.KUBERO_READONLY == 'true'){ - console.log('KUBERO_READONLY is set to true, not deleting app '+appName+' in '+ pipelineName+' phase: '+phaseName); - return; + public async deleteApp( + pipelineName: string, + phaseName: string, + appName: string, + user: IUser, + ) { + this.logger.debug( + 'delete App: ' + appName + ' in ' + pipelineName + ' phase: ' + phaseName, + ); + + if (process.env.KUBERO_READONLY == 'true') { + console.log( + 'KUBERO_READONLY is set to true, not deleting app ' + + appName + + ' in ' + + pipelineName + + ' phase: ' + + phaseName, + ); + return; } - const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + const contextName = await this.pipelinesService.getContext( + pipelineName, + phaseName, + ); if (contextName) { - await this.kubectl.deleteApp(pipelineName, phaseName, appName, contextName); - //this.removeAppFromState(pipelineName, phaseName, appName); - - const m = { - 'name': 'deleteApp', - 'user': user.username, - 'resource': 'app', - 'action': 'delete', - 'severity': 'normal', - 'message': 'Deleted app: '+appName+' in '+ pipelineName+' phase: '+phaseName, - 'pipelineName':pipelineName, - 'phaseName': phaseName, - 'appName': appName, - 'data': {} - } as INotification; - this.NotificationsService.send(m); + await this.kubectl.deleteApp( + pipelineName, + phaseName, + appName, + contextName, + ); + //this.removeAppFromState(pipelineName, phaseName, appName); + + const m = { + name: 'deleteApp', + user: user.username, + resource: 'app', + action: 'delete', + severity: 'normal', + message: + 'Deleted app: ' + + appName + + ' in ' + + pipelineName + + ' phase: ' + + phaseName, + pipelineName: pipelineName, + phaseName: phaseName, + appName: appName, + data: {}, + } as INotification; + this.NotificationsService.send(m); } } - public async createPRApp(branch: string, title: string, ssh_url: string, pipelineName: string | undefined) { - + public async createPRApp( + branch: string, + title: string, + ssh_url: string, + pipelineName: string | undefined, + ) { const podSizeList = await this.settingsService.getPodSizes(); - if ( process.env.KUBERO_READONLY == 'true'){ - console.log('KUBERO_READONLY is set to true, not creating PR app '+title+' in '+ branch+' pipeline: '+pipelineName); - return; + if (process.env.KUBERO_READONLY == 'true') { + console.log( + 'KUBERO_READONLY is set to true, not creating PR app ' + + title + + ' in ' + + branch + + ' pipeline: ' + + pipelineName, + ); + return; } this.logger.debug('createPRApp: ', branch, title, ssh_url); - let pipelines = await this.pipelinesService.listPipelines() as IPipelineList; + const pipelines = await this.pipelinesService.listPipelines() as IPipelineList; for (const pipeline of pipelines.items) { - console.log(pipeline.git.repository?.ssh_url, ssh_url); - console.log(pipeline.reviewapps); - - if (pipeline.reviewapps && - pipeline.git.repository && - pipeline.git.repository.ssh_url === ssh_url) { - - if (pipelineName && pipelineName != pipeline.name) { - continue; - } - - this.logger.debug('found pipeline: '+pipeline.name); - let pipelaneName = pipeline.name - let phaseName = 'review'; - let websaveTitle = title.toLowerCase().replace(/[^a-z0-9-]/g, '-'); //TODO improve websave title - - let appOptions:IApp = { - name: websaveTitle, - pipeline: pipelaneName, - sleep: 'disabled', //TODO use config value. This is BETA and should be disabled by default - gitrepo: pipeline.git.repository, - buildpack: pipeline.buildpack.name, - deploymentstrategy: pipeline.deploymentstrategy, - buildstrategy: 'plain', // TODO: use buildstrategy from pipeline - phase: phaseName, - branch: branch, - autodeploy: true, - podsize: podSizeList[0], //TODO select from podsizelist - autoscale: false, - basicAuth: { - enabled: false, - realm: '', - accounts: [] - }, - envVars: pipeline.phases.find(p => p.name == phaseName)?.defaultEnvvars || [], - extraVolumes: [], //TODO Not sure how to handlle extra Volumes on PR Apps - serviceAccount: { - annotations: {}, - create: false, - name: '' - }, - image: { - containerPort: 8080, //TODO use custom containerport - repository: pipeline.dockerimage, // FIXME: Maybe needs a lookup into buildpack - tag: "main", - command: [''], - pullPolicy: "Always", - fetch: pipeline.buildpack.fetch, - build: pipeline.buildpack.build, - run: pipeline.buildpack.run, - }, - web: { - replicaCount: 1, - autoscaling: { - minReplicas: 0, - maxReplicas: 0, - targetCPUUtilizationPercentage: 0, - targetMemoryUtilizationPercentage: 0 - } - }, - worker: { - replicaCount: 0, // TODO should be dynamic - autoscaling: { - minReplicas: 0, - maxReplicas: 0, - targetCPUUtilizationPercentage: 0, - targetMemoryUtilizationPercentage: 0 - } - }, - cronjobs: [], - addons: [], - resources: {}, - vulnerabilityscan: { - enabled: false, - schedule: "0 0 * * *", - image: { - repository: "aquasec/trivy", - tag: "latest" - } - }, - ingress: { - annotations: {}, - className: process.env.INGRESS_CLASSNAME || 'nginx', - enabled: true, - hosts: [ - { - host: websaveTitle+"."+pipeline.phases.find(p => p.name == phaseName)?.domain, - paths: [ - { - path: "/", - pathType: "Prefix" - } - ] - } - ], - tls: [] - }, - healthcheck: { - enabled: false, - path: "/", - startupSeconds: 90, - timeoutSeconds: 3, - periodSeconds: 10 - }, - } - let app = new App(appOptions); - - //TODO: Logad git user - const user = { - username: 'unknown' - } as IUser; - - this.createApp(app, user); - return { status: 'ok', message: 'app created '+app.name }; + console.log(pipeline.git.repository?.ssh_url, ssh_url); + console.log(pipeline.reviewapps); + + if ( + pipeline.reviewapps && + pipeline.git.repository && + pipeline.git.repository.ssh_url === ssh_url + ) { + if (pipelineName && pipelineName != pipeline.name) { + continue; } + + this.logger.debug('found pipeline: ' + pipeline.name); + let pipelaneName = pipeline.name; + let phaseName = 'review'; + let websaveTitle = title.toLowerCase().replace(/[^a-z0-9-]/g, '-'); //TODO improve websave title + + let appOptions: IApp = { + name: websaveTitle, + pipeline: pipelaneName, + sleep: 'disabled', //TODO use config value. This is BETA and should be disabled by default + gitrepo: pipeline.git.repository, + buildpack: pipeline.buildpack.name, + deploymentstrategy: pipeline.deploymentstrategy, + buildstrategy: 'plain', // TODO: use buildstrategy from pipeline + phase: phaseName, + branch: branch, + autodeploy: true, + podsize: podSizeList[0], //TODO select from podsizelist + autoscale: false, + basicAuth: { + enabled: false, + realm: '', + accounts: [], + }, + envVars: + pipeline.phases.find((p) => p.name == phaseName)?.defaultEnvvars || + [], + extraVolumes: [], //TODO Not sure how to handlle extra Volumes on PR Apps + serviceAccount: { + annotations: {}, + create: false, + name: '', + }, + image: { + containerPort: 8080, //TODO use custom containerport + repository: pipeline.dockerimage, // FIXME: Maybe needs a lookup into buildpack + tag: 'main', + command: [''], + pullPolicy: 'Always', + fetch: pipeline.buildpack.fetch, + build: pipeline.buildpack.build, + run: pipeline.buildpack.run, + }, + web: { + replicaCount: 1, + autoscaling: { + minReplicas: 0, + maxReplicas: 0, + targetCPUUtilizationPercentage: 0, + targetMemoryUtilizationPercentage: 0, + }, + }, + worker: { + replicaCount: 0, // TODO should be dynamic + autoscaling: { + minReplicas: 0, + maxReplicas: 0, + targetCPUUtilizationPercentage: 0, + targetMemoryUtilizationPercentage: 0, + }, + }, + cronjobs: [], + addons: [], + resources: {}, + vulnerabilityscan: { + enabled: false, + schedule: '0 0 * * *', + image: { + repository: 'aquasec/trivy', + tag: 'latest', + }, + }, + ingress: { + annotations: {}, + className: process.env.INGRESS_CLASSNAME || 'nginx', + enabled: true, + hosts: [ + { + host: + websaveTitle + + '.' + + pipeline.phases.find((p) => p.name == phaseName)?.domain, + paths: [ + { + path: '/', + pathType: 'Prefix', + }, + ], + }, + ], + tls: [], + }, + healthcheck: { + enabled: false, + path: '/', + startupSeconds: 90, + timeoutSeconds: 3, + periodSeconds: 10, + }, + }; + let app = new App(appOptions); + + //TODO: Logad git user + const user = { + username: 'unknown', + } as IUser; + + this.createApp(app, user); + return { status: 'ok', message: 'app created ' + app.name }; + } } } - public async getTemplate(pipelineName: string, phaseName: string, appName: string ) { + public async getTemplate( + pipelineName: string, + phaseName: string, + appName: string, + ) { const app = await this.getApp(pipelineName, phaseName, appName); - + const a = app as IKubectlApp; - let t = new KubectlTemplate(a.spec as IApp); + const t = new KubectlTemplate(a.spec as IApp); //Convert template to Yaml - const template = this.YAML.stringify(t, {indent: 4, resolveKnownTags: true}); + const template = this.YAML.stringify(t, { + indent: 4, + resolveKnownTags: true, + }); - return template + return template; } - public async restartApp(pipelineName: string, phaseName: string, appName: string, user: IUser) { - - if ( process.env.KUBERO_READONLY == 'true'){ - console.log('KUBERO_READONLY is set to true, not restarting app'+appName+' in '+ pipelineName+' phase: '+phaseName); - return; + public async restartApp( + pipelineName: string, + phaseName: string, + appName: string, + user: IUser, + ) { + if (process.env.KUBERO_READONLY == 'true') { + console.log( + 'KUBERO_READONLY is set to true, not restarting app' + + appName + + ' in ' + + pipelineName + + ' phase: ' + + phaseName, + ); + return; } - this.logger.debug('restart App: '+appName+' in '+ pipelineName+' phase: '+phaseName); - const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + this.logger.debug( + 'restart App: ' + + appName + + ' in ' + + pipelineName + + ' phase: ' + + phaseName, + ); + const contextName = await this.pipelinesService.getContext( + pipelineName, + phaseName, + ); if (contextName) { - this.kubectl.restartApp(pipelineName, phaseName, appName, 'web', contextName); - this.kubectl.restartApp(pipelineName, phaseName, appName, 'worker', contextName); - - const m = { - 'name': 'restartApp', - 'user': user.username, - 'resource': 'app', - 'action': 'restart', - 'severity': 'normal', - 'message': 'Restarted app: '+appName+' in '+ pipelineName+' phase: '+phaseName, - 'pipelineName': pipelineName, - 'phaseName': phaseName, - 'appName': appName, - 'data': {} - } as INotification; - this.NotificationsService.send(m); + this.kubectl.restartApp( + pipelineName, + phaseName, + appName, + 'web', + contextName, + ); + this.kubectl.restartApp( + pipelineName, + phaseName, + appName, + 'worker', + contextName, + ); + + const m = { + name: 'restartApp', + user: user.username, + resource: 'app', + action: 'restart', + severity: 'normal', + message: + 'Restarted app: ' + + appName + + ' in ' + + pipelineName + + ' phase: ' + + phaseName, + pipelineName: pipelineName, + phaseName: phaseName, + appName: appName, + data: {}, + } as INotification; + this.NotificationsService.send(m); } } + // update an app in a pipeline and phase + public async updateApp(app: App, resourceVersion: string, user: IUser) { + this.logger.debug( + 'update App: ' + + app.name + + ' in ' + + app.pipeline + + ' phase: ' + + app.phase, + ); + //await this.pipelinesService.setContext(app.pipeline, app.phase); + + if (process.env.KUBERO_READONLY == 'true') { + console.log( + 'KUBERO_READONLY is set to true, not updating app ' + app.name, + ); + return; + } - // update an app in a pipeline and phase - public async updateApp(app: App, resourceVersion: string, user: IUser) { - this.logger.debug('update App: '+app.name+' in '+ app.pipeline+' phase: '+app.phase); - //await this.pipelinesService.setContext(app.pipeline, app.phase); - - if ( process.env.KUBERO_READONLY == 'true'){ - console.log('KUBERO_READONLY is set to true, not updating app ' + app.name); - return; - } - - const contextName = await this.pipelinesService.getContext(app.pipeline, app.phase); - - if (app.deploymentstrategy == 'git' && (app.buildstrategy == 'dockerfile' || app.buildstrategy == 'nixpacks' || app.buildstrategy == 'buildpacks')){ - this.triggerImageBuild(app.pipeline, app.phase, app.name); - } + const contextName = await this.pipelinesService.getContext( + app.pipeline, + app.phase, + ); + + if ( + app.deploymentstrategy == 'git' && + (app.buildstrategy == 'dockerfile' || + app.buildstrategy == 'nixpacks' || + app.buildstrategy == 'buildpacks') + ) { + this.triggerImageBuild(app.pipeline, app.phase, app.name); + } - if (contextName) { - await this.kubectl.updateApp(app, resourceVersion, contextName); - // IMPORTANT TODO : Update this.appStateList !! - - const m = { - 'name': 'updateApp', - 'user': user.username, - 'resource': 'app', - 'action': 'update', - 'severity': 'normal', - 'message': 'Updated app: '+app.name+' in '+ app.pipeline+' phase: '+app.phase, - 'pipelineName':app.pipeline, - 'phaseName': app.phase, - 'appName': app.name, - 'data': { - 'app': app - } - } as INotification; - this.NotificationsService.send(m); - } + if (contextName) { + await this.kubectl.updateApp(app, resourceVersion, contextName); + // IMPORTANT TODO : Update this.appStateList !! + + const m = { + name: 'updateApp', + user: user.username, + resource: 'app', + action: 'update', + severity: 'normal', + message: + 'Updated app: ' + + app.name + + ' in ' + + app.pipeline + + ' phase: ' + + app.phase, + pipelineName: app.pipeline, + phaseName: app.phase, + appName: app.name, + data: { + app: app, + }, + } as INotification; + this.NotificationsService.send(m); } + } } diff --git a/server-refactored-v3/src/audit/audit.controller.ts b/server-refactored-v3/src/audit/audit.controller.ts index b4da3b98..fbeec9df 100644 --- a/server-refactored-v3/src/audit/audit.controller.ts +++ b/server-refactored-v3/src/audit/audit.controller.ts @@ -1,4 +1,11 @@ -import { Controller, DefaultValuePipe, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { + Controller, + DefaultValuePipe, + Get, + Param, + ParseIntPipe, + Query, +} from '@nestjs/common'; import { AuditService } from './audit.service'; import { ApiOperation } from '@nestjs/swagger'; @@ -12,7 +19,12 @@ export class AuditController { @Param('pipeline') pipeline: string, @Param('phase') phase: string, @Param('app') app: string, - @Query('limit', new DefaultValuePipe('100'), new ParseIntPipe({ optional: true })) limit: number, + @Query( + 'limit', + new DefaultValuePipe('100'), + new ParseIntPipe({ optional: true }), + ) + limit: number, ) { return this.auditService.getAppEntries(pipeline, phase, app, limit); } @@ -20,7 +32,12 @@ export class AuditController { @ApiOperation({ summary: 'Get all audit entries' }) @Get('/') async getAuditAll( - @Query('limit', new DefaultValuePipe('100'), new ParseIntPipe({ optional: true })) limit: number, + @Query( + 'limit', + new DefaultValuePipe('100'), + new ParseIntPipe({ optional: true }), + ) + limit: number, ) { return this.auditService.get(limit); } diff --git a/server-refactored-v3/src/audit/audit.interface.ts b/server-refactored-v3/src/audit/audit.interface.ts index fba8887e..f87bd66e 100644 --- a/server-refactored-v3/src/audit/audit.interface.ts +++ b/server-refactored-v3/src/audit/audit.interface.ts @@ -1,11 +1,27 @@ export interface AuditEntry { - user: string, - severity: "normal" | "info" | "warning" | "critical" | "error" | "unknown", - action: string, - resource: "system" | "app" | "pipeline" | "phase" | "namespace" | "build" | "addon" | "settings" | "user" | "events" | "security" | "templates" | "config" | "addons" | "kubernetes" | "unknown", - namespace: string, - phase: string, - app: string, - pipeline: string, - message: string, -} \ No newline at end of file + user: string; + severity: 'normal' | 'info' | 'warning' | 'critical' | 'error' | 'unknown'; + action: string; + resource: + | 'system' + | 'app' + | 'pipeline' + | 'phase' + | 'namespace' + | 'build' + | 'addon' + | 'settings' + | 'user' + | 'events' + | 'security' + | 'templates' + | 'config' + | 'addons' + | 'kubernetes' + | 'unknown'; + namespace: string; + phase: string; + app: string; + pipeline: string; + message: string; +} diff --git a/server-refactored-v3/src/audit/audit.service.ts b/server-refactored-v3/src/audit/audit.service.ts index 7cf89034..b87ba981 100644 --- a/server-refactored-v3/src/audit/audit.service.ts +++ b/server-refactored-v3/src/audit/audit.service.ts @@ -1,12 +1,11 @@ import { Injectable } from '@nestjs/common'; import { AuditEntry } from './audit.interface'; import { Logger } from '@nestjs/common'; -​import { Database } from 'sqlite3'; +import { Database } from 'sqlite3'; import * as fs from 'fs'; @Injectable() export class AuditService { - private db: Database | undefined; private logmaxbackups: number = 1000; private enabled: boolean = true; @@ -15,54 +14,59 @@ export class AuditService { constructor() { this.dbpath = process.env.KUBERO_AUDIT_DB_PATH || './db'; - this.logmaxbackups = process.env.KUBERO_AUDIT_LIMIT ? parseInt(process.env.KUBERO_AUDIT_LIMIT) : 1000; + this.logmaxbackups = process.env.KUBERO_AUDIT_LIMIT + ? parseInt(process.env.KUBERO_AUDIT_LIMIT) + : 1000; if (process.env.KUBERO_AUDIT !== 'true') { - this.enabled = false; - Logger.log('⏸️ Audit logging not enabled', 'Feature'); - return; + this.enabled = false; + Logger.log('⏸️ Audit logging not enabled', 'Feature'); + return; } - this.init() + this.init(); } public async init() { - if (!this.enabled) { - return; - } + if (!this.enabled) { + return; + } - if (!fs.existsSync(this.dbpath)){ - try { - fs.mkdirSync(this.dbpath); - } catch (error) { - console.error(error); - } + if (!fs.existsSync(this.dbpath)) { + try { + fs.mkdirSync(this.dbpath); + } catch (error) { + console.error(error); } - this.db = new Database(this.dbpath + '/kubero.db', (err) => { - if (err) { - this.logger.error('❌ Audit logging failed to create local sqlite database', err.message); - } - Logger.log('✅ Audit logging enabled', 'Feature'); - this.createTables(); - - const auditEntry: AuditEntry = { - user: 'kubero', - severity: 'normal', - action: 'start', - namespace: '', - phase: '', - app: '', - pipeline: '', - resource: 'system', - message: 'server started', - } + } + this.db = new Database(this.dbpath + '/kubero.db', (err) => { + if (err) { + this.logger.error( + '❌ Audit logging failed to create local sqlite database', + err.message, + ); + } + Logger.log('✅ Audit logging enabled', 'Feature'); + this.createTables(); - this.log(auditEntry); + const auditEntry: AuditEntry = { + user: 'kubero', + severity: 'normal', + action: 'start', + namespace: '', + phase: '', + app: '', + pipeline: '', + resource: 'system', + message: 'server started', + }; - }); + this.log(auditEntry); + }); } private createTables() { - this.db?.run(`CREATE TABLE IF NOT EXISTS audit ( + this.db?.run( + `CREATE TABLE IF NOT EXISTS audit ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, user TEXT, @@ -73,25 +77,28 @@ export class AuditService { pipeline TEXT, resource TEXT, message TEXT - )`, (err) => { - if (err) { - this.logger.error(err); - } - }); + )`, + (err) => { + if (err) { + this.logger.error(err); + } + }, + ); } public logDelayed(entry: AuditEntry, delay: number = 1000) { - setTimeout(() => { - this.log(entry); - }, delay); + setTimeout(() => { + this.log(entry); + }, delay); } public log(entry: AuditEntry) { - //this.logger.debug(entry) - if (!this.enabled) { - return; - } - this.db?.run(`INSERT INTO audit ( + //this.logger.debug(entry) + if (!this.enabled) { + return; + } + this.db?.run( + `INSERT INTO audit ( user, action, namespace, @@ -100,171 +107,215 @@ export class AuditService { pipeline, resource, message - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ - entry.user, - entry.action, - entry.namespace, - entry.phase, - entry.app, - entry.pipeline, - entry.resource, - entry.message - ], (err) => { - if (err) { - this.logger.error(err); - } - } - ); - - this.limit(this.logmaxbackups); + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [ + entry.user, + entry.action, + entry.namespace, + entry.phase, + entry.app, + entry.pipeline, + entry.resource, + entry.message, + ], + (err) => { + if (err) { + this.logger.error(err); + } + }, + ); + + this.limit(this.logmaxbackups); } - public get(limit: number = 100): Promise<{audit: AuditEntry[], count: number, limit: number}> { - if (!this.enabled) { - return new Promise((resolve) => { - resolve({audit: [], count: 0, limit: limit}); - }); - } - return new Promise((resolve, reject) => { - this.db?.all(`SELECT * FROM audit ORDER BY timestamp DESC LIMIT ?`, [limit], (err, rows) => { - if (err) { - reject(err); - } - resolve({audit: rows as AuditEntry[], count: rows.length, limit: limit}); - }); + public get( + limit: number = 100, + ): Promise<{ audit: AuditEntry[]; count: number; limit: number }> { + if (!this.enabled) { + return new Promise((resolve) => { + resolve({ audit: [], count: 0, limit: limit }); }); + } + return new Promise((resolve, reject) => { + this.db?.all( + `SELECT * FROM audit ORDER BY timestamp DESC LIMIT ?`, + [limit], + (err, rows) => { + if (err) { + reject(err); + } + resolve({ + audit: rows as AuditEntry[], + count: rows.length, + limit: limit, + }); + }, + ); + }); } - public getFiltered(limit: number = 100, filter: string = ''): Promise { - if (!this.enabled) { - return new Promise((resolve) => { - resolve([]); - }); - } - return new Promise((resolve, reject) => { - this.db?.all(`SELECT * FROM audit WHERE message LIKE ? ORDER BY timestamp DESC LIMIT ?`, ['%'+filter+'%', limit], (err, rows) => { - if (err) { - reject(err); - } - resolve(rows as AuditEntry[]); - }); + public getFiltered( + limit: number = 100, + filter: string = '', + ): Promise { + if (!this.enabled) { + return new Promise((resolve) => { + resolve([]); }); + } + return new Promise((resolve, reject) => { + this.db?.all( + `SELECT * FROM audit WHERE message LIKE ? ORDER BY timestamp DESC LIMIT ?`, + ['%' + filter + '%', limit], + (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }, + ); + }); } - public getAppEntries(pipeline: string, phase: string, app: string, limit: number = 100): Promise { - if (!this.enabled) { - return new Promise((resolve) => { - resolve([]); - }); - } - return new Promise((resolve, reject) => { - this.db?.all(`SELECT * FROM audit WHERE pipeline = ? AND phase = ? AND app = ? ORDER BY timestamp DESC LIMIT ?`, [pipeline, phase, app, limit], (err, rows) => { - if (err) { - reject(err); - } - resolve(rows as AuditEntry[]); - }); + public getAppEntries( + pipeline: string, + phase: string, + app: string, + limit: number = 100, + ): Promise { + if (!this.enabled) { + return new Promise((resolve) => { + resolve([]); }); - }; + } + return new Promise((resolve, reject) => { + this.db?.all( + `SELECT * FROM audit WHERE pipeline = ? AND phase = ? AND app = ? ORDER BY timestamp DESC LIMIT ?`, + [pipeline, phase, app, limit], + (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }, + ); + }); + } - public getPhaseEntries(phase: string, limit: number = 100): Promise { - if (!this.enabled) { - return new Promise((resolve, reject) => { - resolve([]); - }); - } + public getPhaseEntries( + phase: string, + limit: number = 100, + ): Promise { + if (!this.enabled) { return new Promise((resolve, reject) => { - this.db?.all(`SELECT * FROM audit WHERE phase = ? ORDER BY timestamp DESC LIMIT ?`, [phase, limit], (err, rows) => { - if (err) { - reject(err); - } - resolve(rows as AuditEntry[]); - }); + resolve([]); }); - }; + } + return new Promise((resolve, reject) => { + this.db?.all( + `SELECT * FROM audit WHERE phase = ? ORDER BY timestamp DESC LIMIT ?`, + [phase, limit], + (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }, + ); + }); + } - public getPipelineEntries(pipeline: string, limit: number = 100): Promise { - if (!this.enabled) { - return new Promise((resolve, reject) => { - resolve([]); - }); - } + public getPipelineEntries( + pipeline: string, + limit: number = 100, + ): Promise { + if (!this.enabled) { return new Promise((resolve, reject) => { - this.db?.all(`SELECT * FROM audit WHERE pipeline = ? ORDER BY timestamp DESC LIMIT ?`, [pipeline, limit], (err, rows) => { - if (err) { - reject(err); - } - resolve(rows as AuditEntry[]); - }); + resolve([]); }); - }; + } + return new Promise((resolve, reject) => { + this.db?.all( + `SELECT * FROM audit WHERE pipeline = ? ORDER BY timestamp DESC LIMIT ?`, + [pipeline, limit], + (err, rows) => { + if (err) { + reject(err); + } + resolve(rows as AuditEntry[]); + }, + ); + }); + } private flush(): Promise { - return new Promise((resolve, reject) => { - this.db?.run(`DELETE FROM audit`, (err) => { - if (err) { - reject(err); - } - resolve(); - }); + return new Promise((resolve, reject) => { + this.db?.run(`DELETE FROM audit`, (err) => { + if (err) { + reject(err); + } + resolve(); }); + }); } private close(): Promise { - return new Promise((resolve, reject) => { - this.db?.close((err) => { - if (err) { - reject(err); - } - resolve(); - }); + return new Promise((resolve, reject) => { + this.db?.close((err) => { + if (err) { + reject(err); + } + resolve(); }); + }); } public async reset(): Promise { - if (!this.enabled) { - return; + if (!this.enabled) { + return; + } + await this.flush(); + await this.close(); + fs.unlinkSync('./db/kubero.db'); + this.db = new Database('./db/kubero.db', (err) => { + if (err) { + this.logger.error(err.message); } - await this.flush(); - await this.close(); - fs.unlinkSync('./db/kubero.db'); - this.db = new Database('./db/kubero.db', (err) => { - if (err) { - this.logger.error(err.message); - } - this.logger.log('Connected to the kubero database.'); - }); - this.createTables(); + this.logger.log('Connected to the kubero database.'); + }); + this.createTables(); } // remove the oldest entries from database if the limit is reached private limit = (limit: number = 1000) => { - this.db?.run(`DELETE FROM audit WHERE id IN (SELECT id FROM audit ORDER BY timestamp DESC LIMIT -1 OFFSET ?)`, [limit], (err) => { - if (err) { - this.logger.error(err); - } - }) - } + this.db?.run( + `DELETE FROM audit WHERE id IN (SELECT id FROM audit ORDER BY timestamp DESC LIMIT -1 OFFSET ?)`, + [limit], + (err) => { + if (err) { + this.logger.error(err); + } + }, + ); + }; public count(): Promise { - if (!this.enabled) { - return new Promise((resolve, reject) => { - resolve(0); - }); - } + if (!this.enabled) { return new Promise((resolve, reject) => { - this.db?.get(`SELECT COUNT(*) as entries FROM audit`, (err, row) => { - if (err) { - reject(err); - } - resolve((row as any)['entries'] as number); - }); + resolve(0); + }); + } + return new Promise((resolve, reject) => { + this.db?.get(`SELECT COUNT(*) as entries FROM audit`, (err, row) => { + if (err) { + reject(err); + } + resolve((row as any)['entries'] as number); }); + }); } public getAuditEnabled(): boolean { - return this.enabled; + return this.enabled; } - - -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/auth/auth.controller.ts b/server-refactored-v3/src/auth/auth.controller.ts index 56e951f7..5b63c490 100644 --- a/server-refactored-v3/src/auth/auth.controller.ts +++ b/server-refactored-v3/src/auth/auth.controller.ts @@ -1,4 +1,11 @@ -import { Controller, Request, UseGuards, Post, Get, Response } from '@nestjs/common'; +import { + Controller, + Request, + UseGuards, + Post, + Get, + Response, +} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { AuthService } from './auth.service'; @Controller({ path: 'api/auth', version: '1' }) @@ -14,20 +21,19 @@ export class AuthController { @UseGuards(AuthGuard('local')) async logout(@Request() req, @Response() res) { req.logout({}, function (err: Error) { - if (err) { - throw new Error('Logout failed: Function not implemented.'); - } - res.send("Logged out"); + if (err) { + throw new Error('Logout failed: Function not implemented.'); + } + res.send('Logged out'); } as any); - console.log("logged out") - return res.send("logged out"); + console.log('logged out'); + return res.send('logged out'); } @Get('session') async session(@Request() req, @Response() res) { - const {message, status} = this.authService.getSession(req); + const { message, status } = this.authService.getSession(req); res.status(status); res.send(message); } } - diff --git a/server-refactored-v3/src/auth/auth.interface.ts b/server-refactored-v3/src/auth/auth.interface.ts index 155c1a38..0a62d3d0 100644 --- a/server-refactored-v3/src/auth/auth.interface.ts +++ b/server-refactored-v3/src/auth/auth.interface.ts @@ -1,6 +1,6 @@ -export type IUser = { - id: number, - method: string, - username: string, - apitoken?: string -} \ No newline at end of file +export type IUser = { + id: number; + method: string; + username: string; + apitoken?: string; +}; diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index 8a6893eb..2e566263 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -8,8 +8,8 @@ import { AuthController } from './auth.controller'; import { AuditModule } from 'src/audit/audit.module'; @Module({ - imports: [UsersModule, PassportModule ], + imports: [UsersModule, PassportModule], providers: [AuthService, LocalStrategy, KubernetesModule, AuditModule], controllers: [AuthController], }) -export class AuthModule {} \ No newline at end of file +export class AuthModule {} diff --git a/server-refactored-v3/src/auth/auth.service.ts b/server-refactored-v3/src/auth/auth.service.ts index 470197f3..9ccdf0a2 100644 --- a/server-refactored-v3/src/auth/auth.service.ts +++ b/server-refactored-v3/src/auth/auth.service.ts @@ -22,11 +22,10 @@ export class AuthService { return null; } - getSession(req: Request,): { message: any, status: number } { - - let isAuthenticated = false - let status = 200 -/* + getSession(req: Request): { message: any; status: number } { + const isAuthenticated = false; + const status = 200; + /* if (auth.authentication === true) { isAuthenticated = req.isAuthenticated() if (!isAuthenticated) { @@ -35,20 +34,20 @@ export class AuthService { } */ - let message = { - "isAuthenticated": isAuthenticated, - "version": process.env.npm_package_version, - "kubernetesVersion": this.kubectl.getKubernetesVersion(), - "operatorVersion": this.kubectl.getOperatorVersion(), - "buildPipeline": this.settingsService.getBuildpipelineEnabled(), - "templatesEnabled": this.settingsService.getTemplateEnabled(), - "auditEnabled": this.auditService.getAuditEnabled(), - "adminDisabled": this.settingsService.checkAdminDisabled(), - "consoleEnabled": this.settingsService.getConsoleEnabled(), - "metricsEnabled": this.settingsService.getMetricsEnabled(), - "sleepEnabled": this.settingsService.getSleepEnabled(), - } + const message = { + isAuthenticated: isAuthenticated, + version: process.env.npm_package_version, + kubernetesVersion: this.kubectl.getKubernetesVersion(), + operatorVersion: this.kubectl.getOperatorVersion(), + buildPipeline: this.settingsService.getBuildpipelineEnabled(), + templatesEnabled: this.settingsService.getTemplateEnabled(), + auditEnabled: this.auditService.getAuditEnabled(), + adminDisabled: this.settingsService.checkAdminDisabled(), + consoleEnabled: this.settingsService.getConsoleEnabled(), + metricsEnabled: this.settingsService.getMetricsEnabled(), + sleepEnabled: this.settingsService.getSleepEnabled(), + }; - return { message: message, status: status } + return { message: message, status: status }; } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/auth/local.strategy.ts b/server-refactored-v3/src/auth/local.strategy.ts index 8eb4967f..9bbed389 100644 --- a/server-refactored-v3/src/auth/local.strategy.ts +++ b/server-refactored-v3/src/auth/local.strategy.ts @@ -16,4 +16,4 @@ export class LocalStrategy extends PassportStrategy(Strategy) { } return user; } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/core/core.module.ts b/server-refactored-v3/src/core/core.module.ts index b52ce8a8..a3f0e76c 100644 --- a/server-refactored-v3/src/core/core.module.ts +++ b/server-refactored-v3/src/core/core.module.ts @@ -2,6 +2,6 @@ import { Module } from '@nestjs/common'; import { CoreService } from './core.service'; @Module({ - providers: [CoreService] + providers: [CoreService], }) export class CoreModule {} diff --git a/server-refactored-v3/src/deployments/deployments.controller.ts b/server-refactored-v3/src/deployments/deployments.controller.ts index dc01c40a..660b6bc0 100644 --- a/server-refactored-v3/src/deployments/deployments.controller.ts +++ b/server-refactored-v3/src/deployments/deployments.controller.ts @@ -4,18 +4,15 @@ import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/deployments', version: '1' }) export class DeploymentsController { - constructor( - private readonly deploymentsService: DeploymentsService - ) {} + constructor(private readonly deploymentsService: DeploymentsService) {} @ApiOperation({ summary: 'List deployments for a specific app' }) @Get('/:pipeline/:phase/:app') async getDeployments( @Param('pipeline') pipeline: string, @Param('phase') phase: string, - @Param('app') app: string + @Param('app') app: string, ) { return this.deploymentsService.listBuildjobs(pipeline, phase, app); } - } diff --git a/server-refactored-v3/src/deployments/deployments.interface.ts b/server-refactored-v3/src/deployments/deployments.interface.ts index b62cabdd..d06e967b 100644 --- a/server-refactored-v3/src/deployments/deployments.interface.ts +++ b/server-refactored-v3/src/deployments/deployments.interface.ts @@ -1,112 +1,110 @@ - export type IKuberoBuildjob = { - creationTimestamp: string, - name: string, - app: string, - pipeline: string, - phase: string, //Missing - image: string, - tag: string, - gitrepo: string, - gitref: string, - buildstrategy: string, + creationTimestamp: string; + name: string; + app: string; + pipeline: string; + phase: string; //Missing + image: string; + tag: string; + gitrepo: string; + gitref: string; + buildstrategy: string; - backoffLimit: number, - state: string, - duration: number, - status: { - completionTime?: string, + backoffLimit: number; + state: string; + duration: number; + status: { + completionTime?: string; conditions: Array<{ - lastProbeTime: string - lastTransitionTime: string - message: string - reason: string - status: string - type: string - }> - failed?: number - succeeded?: number - active?: number - ready: number - startTime: string - terminating: number - uncountedTerminatedPods: any - } -} + lastProbeTime: string; + lastTransitionTime: string; + message: string; + reason: string; + status: string; + type: string; + }>; + failed?: number; + succeeded?: number; + active?: number; + ready: number; + startTime: string; + terminating: number; + uncountedTerminatedPods: any; + }; +}; export type KuberoBuild = { - apiVersion: string - kind: string - metadata: { - creationTimestamp?: string - finalizers?: Array - generation?: number - managedFields?: Array - name: string - namespace: string - resourceVersion?: string - uid?: string - } - spec: { - app: string, - pipeline: string - id: string, - buildstrategy: string - buildpack?: { - path: string - cnbPlatformApi: string - } - dockerfile?: { - path: string - } - nixpack?: { - path: string - } - git: { - revision?: string //TODO: Remove - ref?: string - url: string - } - podSecurityContext?: { - fsGroup: number - } - repository: { - image: string - tag: string, - active?: boolean - } - } - status?: { - conditions: Array<{ - lastTransitionTime: string - status: string - type: string - reason?: string - }> - deployedRelease?: { - manifest: string - name: string - } - } - jobstatus?: { - duration?: number // in miliseconds - startTime: string - completionTime?: string - status: "Unknown" | "Active" | "Succeeded" | "Failed" - } - } + apiVersion: string; + kind: string; + metadata: { + creationTimestamp?: string; + finalizers?: Array; + generation?: number; + managedFields?: Array; + name: string; + namespace: string; + resourceVersion?: string; + uid?: string; + }; + spec: { + app: string; + pipeline: string; + id: string; + buildstrategy: string; + buildpack?: { + path: string; + cnbPlatformApi: string; + }; + dockerfile?: { + path: string; + }; + nixpack?: { + path: string; + }; + git: { + revision?: string; //TODO: Remove + ref?: string; + url: string; + }; + podSecurityContext?: { + fsGroup: number; + }; + repository: { + image: string; + tag: string; + active?: boolean; + }; + }; + status?: { + conditions: Array<{ + lastTransitionTime: string; + status: string; + type: string; + reason?: string; + }>; + deployedRelease?: { + manifest: string; + name: string; + }; + }; + jobstatus?: { + duration?: number; // in miliseconds + startTime: string; + completionTime?: string; + status: 'Unknown' | 'Active' | 'Succeeded' | 'Failed'; + }; +}; +export type KuberoBuildList = { + apiVersion: string; + items: Array; + kind: string; + metadata: { + continue: string; + resourceVersion: string; + }; +}; - export type KuberoBuildList = { - apiVersion: string - items: Array - kind: string - metadata: { - continue: string - resourceVersion: string - } - } - /* export interface DeploymentOptions { kubectl: Kubectl; @@ -114,4 +112,4 @@ export interface DeploymentOptions { io: any; kubero: Kubero; } -*/ \ No newline at end of file +*/ diff --git a/server-refactored-v3/src/deployments/deployments.module.ts b/server-refactored-v3/src/deployments/deployments.module.ts index 0c7754e5..05677023 100644 --- a/server-refactored-v3/src/deployments/deployments.module.ts +++ b/server-refactored-v3/src/deployments/deployments.module.ts @@ -7,6 +7,6 @@ import { LogsService } from 'src/logs/logs.service'; @Module({ controllers: [DeploymentsController], - providers: [DeploymentsService, AppsService, EventsGateway, LogsService] + providers: [DeploymentsService, AppsService, EventsGateway, LogsService], }) export class DeploymentsModule {} diff --git a/server-refactored-v3/src/deployments/deployments.service.ts b/server-refactored-v3/src/deployments/deployments.service.ts index 62f8854a..414a16f3 100644 --- a/server-refactored-v3/src/deployments/deployments.service.ts +++ b/server-refactored-v3/src/deployments/deployments.service.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { IKuberoBuildjob } from './deployments.interface'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { IKubectlApp } from '../kubernetes/kubernetes.interface'; -import { NotificationsService } from '../notifications/notifications.service'; +import { NotificationsService } from '../notifications/notifications.service'; import { INotification } from '../notifications/notifications.interface'; import { IUser } from '../auth/auth.interface'; import { AppsService } from '../apps/apps.service'; @@ -18,188 +18,245 @@ export class DeploymentsService { //private kubero: Kubero; constructor( - //options: DeploymentOptions - private kubectl: KubernetesService, - private appsService: AppsService, - private notificationService: NotificationsService, - private pipelinesService: PipelinesService, - private LogsService: LogsService + //options: DeploymentOptions + private kubectl: KubernetesService, + private appsService: AppsService, + private notificationService: NotificationsService, + private pipelinesService: PipelinesService, + private LogsService: LogsService, ) { - //this.kubectl = options.kubectl - //this._io = options.io - //this.notification = options.notifications - //this.kubero = options.kubero + //this.kubectl = options.kubectl + //this._io = options.io + //this.notification = options.notifications + //this.kubero = options.kubero } private logger = new Logger(DeploymentsService.name); - public async listBuildjobs(pipelineName: string, phaseName: string, appName: string): Promise { - const namespace = pipelineName + "-" + phaseName - let jobs = await this.kubectl.getJobs(namespace) as V1JobList - const appresult = await this.appsService.getApp(pipelineName, phaseName, appName) + public async listBuildjobs( + pipelineName: string, + phaseName: string, + appName: string, + ): Promise { + const namespace = pipelineName + '-' + phaseName; + const jobs = (await this.kubectl.getJobs(namespace)) as V1JobList; + const appresult = await this.appsService.getApp( + pipelineName, + phaseName, + appName, + ); - const app = appresult as IKubectlApp; + const app = appresult as IKubectlApp; - if (!jobs) { - this.logger.log('No deployments found') - return { - items: [] - } + if (!jobs) { + this.logger.log('No deployments found'); + return { + items: [], + }; + } + + const retJobs = [] as IKuberoBuildjob[]; + for (const j of jobs.items as any) { + // skip non matching apps + if (j.metadata.labels.kuberoapp != appName) { + continue; } - let retJobs = [] as IKuberoBuildjob[] - for (let j of jobs.items as any) { - - // skip non matching apps - if (j.metadata.labels.kuberoapp != appName) { - continue - } - - const retJob = {} as IKuberoBuildjob - retJob.creationTimestamp = j.metadata.creationTimestamp - retJob.name = j.metadata.name - retJob.app = j.metadata.labels.kuberoapp - retJob.pipeline = j.metadata.labels.kuberopipeline - retJob.phase = j.metadata.labels.kuberophase || '' - retJob.buildstrategy = j.metadata.labels.buildstrategy - retJob.gitrepo = j.spec.template.spec.initContainers[0].env.find((e: any) => e.name == 'GIT_REPOSITORY').value - retJob.gitref = j.spec.template.spec.initContainers[0].env.find((e: any) => e.name == 'GIT_REF').value - retJob.image = j.spec.template.spec.containers[0].env.find((e: any) => e.name == 'REPOSITORY').value - retJob.tag = j.spec.template.spec.containers[0].env.find((e: any) => e.name == 'TAG').value - retJob.backoffLimit = j.spec.backoffLimit - retJob.status = j.status - - if (j.status.failed) { - retJob.state = 'Failed' - retJob.duration = ( new Date(j.status.conditions[0].lastProbeTime).getTime() - new Date(j.status.startTime).getTime() ) - } - if (j.status.active) { - retJob.state = 'Active' - retJob.duration = ( new Date().getTime() - new Date(j.status.startTime).getTime() ) - } - if (j.status.succeeded) { - retJob.state = 'Succeeded' - retJob.duration = ( new Date(j.status.completionTime).getTime() - new Date(j.status.startTime).getTime() ) - } - - retJobs.push(retJob) + const retJob = {} as IKuberoBuildjob; + retJob.creationTimestamp = j.metadata.creationTimestamp; + retJob.name = j.metadata.name; + retJob.app = j.metadata.labels.kuberoapp; + retJob.pipeline = j.metadata.labels.kuberopipeline; + retJob.phase = j.metadata.labels.kuberophase || ''; + retJob.buildstrategy = j.metadata.labels.buildstrategy; + retJob.gitrepo = j.spec.template.spec.initContainers[0].env.find( + (e: any) => e.name == 'GIT_REPOSITORY', + ).value; + retJob.gitref = j.spec.template.spec.initContainers[0].env.find( + (e: any) => e.name == 'GIT_REF', + ).value; + retJob.image = j.spec.template.spec.containers[0].env.find( + (e: any) => e.name == 'REPOSITORY', + ).value; + retJob.tag = j.spec.template.spec.containers[0].env.find( + (e: any) => e.name == 'TAG', + ).value; + retJob.backoffLimit = j.spec.backoffLimit; + retJob.status = j.status; + + if (j.status.failed) { + retJob.state = 'Failed'; + retJob.duration = + new Date(j.status.conditions[0].lastProbeTime).getTime() - + new Date(j.status.startTime).getTime(); + } + if (j.status.active) { + retJob.state = 'Active'; + retJob.duration = + new Date().getTime() - new Date(j.status.startTime).getTime(); + } + if (j.status.succeeded) { + retJob.state = 'Succeeded'; + retJob.duration = + new Date(j.status.completionTime).getTime() - + new Date(j.status.startTime).getTime(); } - return retJobs.reverse() + retJobs.push(retJob); + } + + return retJobs.reverse(); } public async triggerBuildjob( - pipeline: string, - phase: string, - app: string, - buildstrategy: 'buildpacks' | 'dockerfile' | 'nixpacks' | 'plain', - gitrepo: string, - reference: string, - dockerfilePath: string, - user: IUser - ): Promise { - - if ( process.env.KUBERO_READONLY == 'true'){ - this.logger.log('KUBERO_READONLY is set to true, not triggering build for app: '+app + ' in pipeline: '+pipeline); - return; - } + pipeline: string, + phase: string, + app: string, + buildstrategy: 'buildpacks' | 'dockerfile' | 'nixpacks' | 'plain', + gitrepo: string, + reference: string, + dockerfilePath: string, + user: IUser, + ): Promise { + if (process.env.KUBERO_READONLY == 'true') { + this.logger.log( + 'KUBERO_READONLY is set to true, not triggering build for app: ' + + app + + ' in pipeline: ' + + pipeline, + ); + return; + } - const namespace = pipeline + "-" + phase - - if ( process.env.KUBERO_READONLY == 'true'){ - this.logger.log('KUBERO_READONLY is set to true'); - return; - } + const namespace = pipeline + '-' + phase; - // Create the Pipeline CRD - try { - await this.kubectl.createBuildJob( - namespace, - app, - pipeline, - buildstrategy, - dockerfilePath, - { - ref: reference, - url: gitrepo - }, - { - image: process.env.KUBERO_BUILD_REGISTRY + "/" + pipeline + "/" + app, - tag: reference - } - ) - } catch (error) { - this.logger.error('kubectl.createBuildJob: Error creating Kubero build job', error) - } - - const m = { - 'name': 'newBuild', - 'user': user.username, - 'resource': 'pipeline', - 'action': 'created', - 'severity': 'normal', - 'message': 'Created new Build Job: '+app + ' in pipeline: '+pipeline, - 'pipelineName':pipeline, - 'phaseName': '', - 'appName': '', - 'data': { - 'pipeline': pipeline - } - } as INotification; - this.notificationService.send(m); + if (process.env.KUBERO_READONLY == 'true') { + this.logger.log('KUBERO_READONLY is set to true'); + return; + } - return { - message: 'Build started' - } + // Create the Pipeline CRD + try { + await this.kubectl.createBuildJob( + namespace, + app, + pipeline, + buildstrategy, + dockerfilePath, + { + ref: reference, + url: gitrepo, + }, + { + image: process.env.KUBERO_BUILD_REGISTRY + '/' + pipeline + '/' + app, + tag: reference, + }, + ); + } catch (error) { + this.logger.error( + 'kubectl.createBuildJob: Error creating Kubero build job', + error, + ); + } + + const m = { + name: 'newBuild', + user: user.username, + resource: 'pipeline', + action: 'created', + severity: 'normal', + message: 'Created new Build Job: ' + app + ' in pipeline: ' + pipeline, + pipelineName: pipeline, + phaseName: '', + appName: '', + data: { + pipeline: pipeline, + }, + } as INotification; + this.notificationService.send(m); + + return { + message: 'Build started', + }; } - public async deleteBuildjob(pipeline: string, phase: string, app: string, buildName: string, user: IUser): Promise { + public async deleteBuildjob( + pipeline: string, + phase: string, + app: string, + buildName: string, + user: IUser, + ): Promise { + if (process.env.KUBERO_READONLY == 'true') { + this.logger.log( + 'KUBERO_READONLY is set to true, not creating app: ' + + app + + ' in pipeline: ' + + pipeline, + ); + return; + } - if ( process.env.KUBERO_READONLY == 'true'){ - this.logger.log('KUBERO_READONLY is set to true, not creating app: '+app + ' in pipeline: '+pipeline); - return; - } + const namespace = pipeline + '-' + phase; + await this.kubectl.deleteKuberoBuildJob(namespace, buildName); - const namespace = pipeline + "-" + phase - await this.kubectl.deleteKuberoBuildJob(namespace, buildName) - - const m = { - 'name': 'newBuild', - 'user': user.username, - 'resource': 'build', - 'action': 'deleted', - 'severity': 'normal', - 'message': 'Deleted Build Job: '+app + ' in pipeline: '+pipeline, - 'pipelineName':pipeline, - 'phaseName': '', - 'appName': '', - 'data': { - 'pipeline': pipeline - } + const m = { + name: 'newBuild', + user: user.username, + resource: 'build', + action: 'deleted', + severity: 'normal', + message: 'Deleted Build Job: ' + app + ' in pipeline: ' + pipeline, + pipelineName: pipeline, + phaseName: '', + appName: '', + data: { + pipeline: pipeline, + }, } as INotification; this.notificationService.send(m); - return { - message: 'Deployment deleted' - } + return { + message: 'Deployment deleted', + }; } - public async getBuildLogs(pipelineName: string, phaseName: string, appName: string, buildName: string, containerName: string): Promise { - const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); - const namespace = pipelineName+'-'+phaseName; + public async getBuildLogs( + pipelineName: string, + phaseName: string, + appName: string, + buildName: string, + containerName: string, + ): Promise { + const contextName = await this.pipelinesService.getContext( + pipelineName, + phaseName, + ); + const namespace = pipelineName + '-' + phaseName; - let loglines = [] as ILoglines[]; + let loglines = [] as ILoglines[]; - if (contextName) { - const pods = await this.kubectl.getPods(namespace, contextName); - for (const pod of pods) { - //this.logger.log('Fetching logs for pod: ', pod.metadata?.labels?.["job-name"], buildName) - if (pod.metadata?.labels?.kuberoapp == appName && pod.metadata.name && pod.metadata?.labels?.["job-name"] == buildName) { - const ll = await this.LogsService.fetchLogs(namespace, pod.metadata.name, containerName, pipelineName, phaseName, appName) - loglines = loglines.concat(ll); - } + if (contextName) { + const pods = await this.kubectl.getPods(namespace, contextName); + for (const pod of pods) { + //this.logger.log('Fetching logs for pod: ', pod.metadata?.labels?.["job-name"], buildName) + if ( + pod.metadata?.labels?.kuberoapp == appName && + pod.metadata.name && + pod.metadata?.labels?.['job-name'] == buildName + ) { + const ll = await this.LogsService.fetchLogs( + namespace, + pod.metadata.name, + containerName, + pipelineName, + phaseName, + appName, + ); + loglines = loglines.concat(ll); } } + } return loglines; } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/dto/ok.dto.ts b/server-refactored-v3/src/dto/ok.dto.ts index ed594ba2..7b74cb9f 100644 --- a/server-refactored-v3/src/dto/ok.dto.ts +++ b/server-refactored-v3/src/dto/ok.dto.ts @@ -1,10 +1,9 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class OKDTO { - @ApiProperty() status: string; - + @ApiPropertyOptional() message?: string; -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/events/events.gateway.ts b/server-refactored-v3/src/events/events.gateway.ts index 2c7ab015..e39d00ca 100644 --- a/server-refactored-v3/src/events/events.gateway.ts +++ b/server-refactored-v3/src/events/events.gateway.ts @@ -1,10 +1,10 @@ import { - MessageBody, - SubscribeMessage, - WebSocketGateway, - WebSocketServer, - WsResponse, - } from '@nestjs/websockets'; + MessageBody, + SubscribeMessage, + WebSocketGateway, + WebSocketServer, + WsResponse, +} from '@nestjs/websockets'; import { from, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Server } from 'socket.io'; @@ -14,7 +14,6 @@ import { Server } from 'socket.io'; origin: '*', }, }) - export class EventsGateway { @WebSocketServer() server: Server; @@ -22,14 +21,17 @@ export class EventsGateway { // TODO: example implementation of a WebSocket event @SubscribeMessage('events') findAll(@MessageBody() data: any): Observable> { - return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item }))); + return from([1, 2, 3]).pipe( + map((item) => ({ event: 'events', data: item })), + ); } sendEvent(event: string, data: any) { this.server.emit(event, data); } - sendLogline(room: string, logline: any) { //TODO define logline type + sendLogline(room: string, logline: any) { + //TODO define logline type this.server.to(room).emit('log', logline); } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/events/events.module.ts b/server-refactored-v3/src/events/events.module.ts index 0354dfdb..2b2d1cbb 100644 --- a/server-refactored-v3/src/events/events.module.ts +++ b/server-refactored-v3/src/events/events.module.ts @@ -2,6 +2,6 @@ import { Module } from '@nestjs/common'; import { EventsGateway } from './events.gateway'; @Module({ - providers: [EventsGateway] + providers: [EventsGateway], }) -export class EventsModule {} \ No newline at end of file +export class EventsModule {} diff --git a/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts b/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts index 8799e0e5..87f59367 100644 --- a/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts +++ b/server-refactored-v3/src/kubernetes/dto/kubernetes.dto.ts @@ -17,90 +17,88 @@ export class StorageClassDTO { } export class ContextDTO { - @ApiProperty() - cluster: string + cluster: string; @ApiProperty() - name: string + name: string; @ApiProperty() - user: string + user: string; @ApiPropertyOptional() - namespace?: string + namespace?: string; } export class GetEventsDTO { - @ApiProperty() - count: number + count: number; @ApiProperty() - eventTime: any - + eventTime: any; + @ApiProperty() - firstTimestamp: string - + firstTimestamp: string; + @ApiProperty() involvedObject: { - apiVersion: string - kind: string - name: string - namespace: string - resourceVersion: string - uid: string - } - + apiVersion: string; + kind: string; + name: string; + namespace: string; + resourceVersion: string; + uid: string; + }; + @ApiProperty() - lastTimestamp: string - + lastTimestamp: string; + @ApiProperty() - message: string - + message: string; + @ApiProperty() metadata: { - creationTimestamp: string + creationTimestamp: string; managedFields: Array<{ - apiVersion: string - fieldsType: string + apiVersion: string; + fieldsType: string; fieldsV1: { - "f:count": {} - "f:firstTimestamp": {} - "f:involvedObject": {} - "f:lastTimestamp": {} - "f:message": {} - "f:reason": {} - "f:source": { - "f:component": {} - } - "f:type": {} - "f:reportingComponent"?: {} - } - manager: string - operation: string - time: string - }> - name: string - namespace: string - resourceVersion: string - uid: string - } - - @ApiProperty() - reason: string - - @ApiProperty() - reportingComponent: string - - @ApiProperty() - reportingInstance: string - + 'f:count': {}; + 'f:firstTimestamp': {}; + 'f:involvedObject': {}; + 'f:lastTimestamp': {}; + 'f:message': {}; + 'f:reason': {}; + 'f:source': { + 'f:component': {}; + }; + 'f:type': {}; + 'f:reportingComponent'?: {}; + }; + manager: string; + operation: string; + time: string; + }>; + name: string; + namespace: string; + resourceVersion: string; + uid: string; + }; + + @ApiProperty() + reason: string; + + @ApiProperty() + reportingComponent: string; + + @ApiProperty() + reportingInstance: string; + @ApiProperty() source: { - component: string - } - + component: string; + }; + @ApiProperty() - type: string + type: string; } diff --git a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts index 4b4e2d04..7e43abdc 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.controller.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.controller.ts @@ -1,21 +1,25 @@ import { Controller, Get, Query } from '@nestjs/common'; import { KubernetesService } from './kubernetes.service'; import { ApiOperation, ApiOkResponse, ApiResponse } from '@nestjs/swagger'; -import { StorageClassDTO, ContextDTO, GetEventsDTO } from './dto/kubernetes.dto'; +import { + StorageClassDTO, + ContextDTO, + GetEventsDTO, +} from './dto/kubernetes.dto'; @Controller({ path: 'api/kubernetes', version: '1' }) export class KubernetesController { - constructor( - private readonly kubernetesService: KubernetesService - ) {} + constructor(private readonly kubernetesService: KubernetesService) {} - @ApiResponse({ - status: 200, + @ApiResponse({ + status: 200, description: 'List of available contexts', type: GetEventsDTO, - isArray: true + isArray: true, + }) + @ApiOperation({ + summary: 'Get the Kubernetes events in a specific namespace', }) - @ApiOperation({ summary: 'Get the Kubernetes events in a specific namespace' }) @Get('events') async getEvents(@Query('namespace') namespace: string) { return this.kubernetesService.getEvents(namespace); @@ -24,7 +28,7 @@ export class KubernetesController { @ApiOkResponse({ description: 'A List of available storage classes', type: StorageClassDTO, - isArray: true + isArray: true, }) @ApiOperation({ summary: 'Get the available storage classes' }) @Get('storageclasses') @@ -33,11 +37,13 @@ export class KubernetesController { } @ApiOkResponse({ - description: 'Already taken domains', - type: [String], - isArray: true + description: 'Already taken domains', + type: [String], + isArray: true, + }) + @ApiOperation({ + summary: 'Get a list of allredy taken domains on this Kubernets cluster', }) - @ApiOperation({ summary: 'Get a list of allredy taken domains on this Kubernets cluster' }) @Get('domains') async getDomains(): Promise { return this.kubernetesService.getDomains(); @@ -46,11 +52,11 @@ export class KubernetesController { @ApiOkResponse({ description: 'A List of available contexts', type: ContextDTO, - isArray: true + isArray: true, }) @ApiOperation({ summary: 'Get available contexts' }) @Get('/contexts') async getContexts(): Promise { - return this.kubernetesService.getContexts(); + return this.kubernetesService.getContexts(); } } diff --git a/server-refactored-v3/src/kubernetes/kubernetes.interface.ts b/server-refactored-v3/src/kubernetes/kubernetes.interface.ts index b59522db..57322e38 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.interface.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.interface.ts @@ -5,54 +5,53 @@ import { Template } from '../templates/template'; export interface IKubectlPipelineList { apiVersion: string; kind: string; - metadata: IKubectlMetadata, - items: IKubectlPipeline[] + metadata: IKubectlMetadata; + items: IKubectlPipeline[]; } export interface IKubectlPipeline { apiVersion: string; kind: string; - metadata: IKubectlMetadata, - spec: IPipeline + metadata: IKubectlMetadata; + spec: IPipeline; } export interface IKubectlMetadata { creationTimestamp?: Date; generation?: number; //labels?: [Object]; - annotations?: Object; + annotations?: object; labels?: { - 'kubernetes.io/metadata.name'?: String, - manager?: string; - } - managedFields?: [Array: Object]; - name?: String; + 'kubernetes.io/metadata.name'?: string; + manager?: string; + }; + managedFields?: [Array: object]; + name?: string; namespace?: string; resourceVersion?: string; uid?: string; - finalizers?: [Array: Object]; + finalizers?: [Array: object]; } export interface IKubectlAppList { apiVersion: string; - items: IKubectlApp []; + items: IKubectlApp[]; kind: string; - metadata: { continue: string; resourceVersion: string; } + metadata: { continue: string; resourceVersion: string }; } -export interface IKubectlApp -{ +export interface IKubectlApp { apiVersion: string; kind: string; - metadata: IKubectlMetadata - spec: IApp ; + metadata: IKubectlMetadata; + spec: IApp; status: { - conditions: [Array: Object]; + conditions: [Array: object]; deployedRelease?: { name: string; manifest: string; - } - } + }; + }; } export interface IStorageClass { @@ -62,4 +61,4 @@ export interface IStorageClass { volumeBindingMode: string; //allowVolumeExpansion: boolean; //mountOptions: string[]; -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/kubernetes/kubernetes.service.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts index de0567f6..2c30877d 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.service.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -1,36 +1,42 @@ import { Injectable, Logger } from '@nestjs/common'; -import { IKubectlPipelineList, IKubectlPipeline, IKubectlAppList, IKubectlApp, IStorageClass} from './kubernetes.interface'; -import { IPipeline, } from '../pipelines/pipelines.interface'; +import { + IKubectlPipelineList, + IKubectlPipeline, + IKubectlAppList, + IKubectlApp, + IStorageClass, +} from './kubernetes.interface'; +import { IPipeline } from '../pipelines/pipelines.interface'; import { KubectlPipeline } from '../pipelines/pipeline/pipeline'; import { KubectlApp, App } from '../apps/app/app'; import { - KubeConfig, - Exec, - VersionApi, - CoreV1Api, - AppsV1Api, - CustomObjectsApi, - KubernetesListObject, - KubernetesObject, - VersionInfo, - PatchUtils, - Log as KubeLog, - V1Pod, - CoreV1Event, - CoreV1EventList, - V1ConfigMap, - V1Namespace, - Metrics, - PodMetric, - PodMetricsList, - NodeMetric, - StorageV1Api, - BatchV1Api, - NetworkingV1Api, - V1ServiceAccount, - V1Job -} from '@kubernetes/client-node' + KubeConfig, + Exec, + VersionApi, + CoreV1Api, + AppsV1Api, + CustomObjectsApi, + KubernetesListObject, + KubernetesObject, + VersionInfo, + PatchUtils, + Log as KubeLog, + V1Pod, + CoreV1Event, + CoreV1EventList, + V1ConfigMap, + V1Namespace, + Metrics, + PodMetric, + PodMetricsList, + NodeMetric, + StorageV1Api, + BatchV1Api, + NetworkingV1Api, + V1ServiceAccount, + V1Job, +} from '@kubernetes/client-node'; import { WebSocket } from 'ws'; import stream from 'stream'; import internal from 'stream'; @@ -38,518 +44,583 @@ import { IKuberoConfig, IKuberoCRD } from 'src/settings/settings.interface'; @Injectable() export class KubernetesService { - private kc: KubeConfig; - private versionApi: VersionApi = {} as VersionApi; - private coreV1Api: CoreV1Api = {} as CoreV1Api; - private appsV1Api: AppsV1Api = {} as AppsV1Api; - private metricsApi: Metrics = {} as Metrics; - private storageV1Api: StorageV1Api = {} as StorageV1Api; - private batchV1Api: BatchV1Api = {} as BatchV1Api; - private customObjectsApi: CustomObjectsApi = {} as CustomObjectsApi; - private networkingV1Api: NetworkingV1Api = {} as NetworkingV1Api; - public kubeVersion: VersionInfo | void; - public kuberoOperatorVersion: string | undefined; - private patchUtils: PatchUtils = {} as PatchUtils; - public log: KubeLog; - //public config: IKuberoConfig; - private exec: Exec = {} as Exec; - private readonly logger = new Logger(KubernetesService.name); - - constructor() { - 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) { - let buff = Buffer.from(process.env.KUBECONFIG_BASE64, 'base64'); - const kubeconfig = buff.toString('ascii'); - this.kc.loadFromString(kubeconfig); - - this.logger.debug("ℹ️ Kubeconfig loaded from base64"); - } else if(process.env.KUBECONFIG_PATH) { - this.kc.loadFromFile(process.env.KUBECONFIG_PATH); - this.logger.debug("ℹ️ Kubeconfig loaded from file " + process.env.KUBECONFIG_PATH); - } else{ - try { - this.kc.loadFromCluster(); - this.logger.debug("ℹ️ Kubeconfig loaded from cluster"); - } catch (error) { - this.logger.error("❌ Error loading from cluster"); - //this.logger.debug(error); - } - } - - - try { - this.versionApi = this.kc.makeApiClient(VersionApi); - this.coreV1Api = this.kc.makeApiClient(CoreV1Api); - this.appsV1Api = this.kc.makeApiClient(AppsV1Api); - this.storageV1Api = this.kc.makeApiClient(StorageV1Api); - this.batchV1Api = this.kc.makeApiClient(BatchV1Api); - this.networkingV1Api = this.kc.makeApiClient(NetworkingV1Api); - this.metricsApi = new Metrics(this.kc); - this.patchUtils = new PatchUtils(); - this.exec = new Exec(this.kc) - this.customObjectsApi = this.kc.makeApiClient(CustomObjectsApi); - } catch (error) { - this.logger.error("❌ Error creating api clients. Check kubeconfig, cluster connectivity and context"); - //this.logger.debug(error); - this.kubeVersion = void 0; - return; - } - - this.loadKubeVersion() - .then(v => { - if (v && v.gitVersion) { - this.logger.debug("ℹ️ Kube version: " + v.gitVersion); - } else { - this.logger.error("❌ Failed to get Kubernetes version"); - process.env.KUBERO_SETUP = 'enabled'; - } - this.kubeVersion = v; - }) - .catch(error => { - this.logger.error("❌ Failed to get Kubernetes version"); - //this.logger.debug(error); - }); - - this.loadOperatorVersion() - .then(v => { - this.logger.debug("ℹ️ Operator version: " + v); - this.kuberoOperatorVersion = v || 'unknown'; - }) - } - - public getKubeVersion(): VersionInfo | void { - return this.kubeVersion; + private kc: KubeConfig; + private versionApi: VersionApi = {} as VersionApi; + private coreV1Api: CoreV1Api = {} as CoreV1Api; + private appsV1Api: AppsV1Api = {} as AppsV1Api; + private metricsApi: Metrics = {} as Metrics; + private storageV1Api: StorageV1Api = {} as StorageV1Api; + private batchV1Api: BatchV1Api = {} as BatchV1Api; + private customObjectsApi: CustomObjectsApi = {} as CustomObjectsApi; + private networkingV1Api: NetworkingV1Api = {} as NetworkingV1Api; + public kubeVersion: VersionInfo | void; + public kuberoOperatorVersion: string | undefined; + private patchUtils: PatchUtils = {} as PatchUtils; + public log: KubeLog; + //public config: IKuberoConfig; + private exec: Exec = {} as Exec; + private readonly logger = new Logger(KubernetesService.name); + + constructor() { + 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) { + const buff = Buffer.from(process.env.KUBECONFIG_BASE64, 'base64'); + const kubeconfig = buff.toString('ascii'); + this.kc.loadFromString(kubeconfig); + + this.logger.debug('ℹ️ Kubeconfig loaded from base64'); + } else if (process.env.KUBECONFIG_PATH) { + this.kc.loadFromFile(process.env.KUBECONFIG_PATH); + this.logger.debug( + 'ℹ️ Kubeconfig loaded from file ' + process.env.KUBECONFIG_PATH, + ); + } else { + try { + this.kc.loadFromCluster(); + this.logger.debug('ℹ️ Kubeconfig loaded from cluster'); + } catch (error) { + this.logger.error('❌ Error loading from cluster'); + //this.logger.debug(error); + } } - public getKubernetesVersion(): string { - return this.kubeVersion?.gitVersion || 'unknown'; + try { + this.versionApi = this.kc.makeApiClient(VersionApi); + this.coreV1Api = this.kc.makeApiClient(CoreV1Api); + this.appsV1Api = this.kc.makeApiClient(AppsV1Api); + this.storageV1Api = this.kc.makeApiClient(StorageV1Api); + this.batchV1Api = this.kc.makeApiClient(BatchV1Api); + this.networkingV1Api = this.kc.makeApiClient(NetworkingV1Api); + this.metricsApi = new Metrics(this.kc); + this.patchUtils = new PatchUtils(); + this.exec = new Exec(this.kc); + this.customObjectsApi = this.kc.makeApiClient(CustomObjectsApi); + } catch (error) { + this.logger.error( + '❌ Error creating api clients. Check kubeconfig, cluster connectivity and context', + ); + //this.logger.debug(error); + this.kubeVersion = void 0; + return; } - public async loadKubeVersion(): 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)); - return versionInfo.body; - } catch (error) { - this.logger.debug("getKubeVersion: error getting kube version"); - //this.logger.debug(error); - } - } - - public getOperatorVersion(): string | undefined { - return this.kuberoOperatorVersion - } - - private async loadOperatorVersion(): Promise { - const contextName = this.getCurrentContext(); - const namespace = "kubero-operator-system"; - - if (contextName) { - const pods = await this.getPods(namespace, contextName) - .catch(error => { - this.logger.debug("❌ Failed to get Operator Version"); - //this.logger.debug(error); - //return 'error'; - }); - if (pods) { - for (const pod of pods) { - if (pod?.metadata?.name?.startsWith('kubero-operator-controller-manager')) { - const container = pod?.spec?.containers.filter((c: any) => c.name == 'manager')[0]; - return container?.image?.split(':')[1] || 'unknown'; - } - } - }else{ - return 'error getting operator version'; - } + this.loadKubeVersion() + .then((v) => { + if (v && v.gitVersion) { + this.logger.debug('ℹ️ Kube version: ' + v.gitVersion); + } else { + this.logger.error('❌ Failed to get Kubernetes version'); + process.env.KUBERO_SETUP = 'enabled'; } + this.kubeVersion = v; + }) + .catch((error) => { + this.logger.error('❌ Failed to get Kubernetes version'); + //this.logger.debug(error); + }); + + this.loadOperatorVersion().then((v) => { + this.logger.debug('ℹ️ Operator version: ' + v); + this.kuberoOperatorVersion = v || 'unknown'; + }); + } + + public getKubeVersion(): VersionInfo | void { + return this.kubeVersion; + } + + public getKubernetesVersion(): string { + return this.kubeVersion?.gitVersion || 'unknown'; + } + + public async loadKubeVersion(): Promise { + // TODO and WARNING: This does not respect the context set by the user! + try { + const versionInfo = await this.versionApi.getCode(); + //debug.debug(JSON.stringify(versionInfo.body)); + return versionInfo.body; + } catch (error) { + this.logger.debug('getKubeVersion: error getting kube version'); + //this.logger.debug(error); } - - public getContexts() { - return this.kc.getContexts() - } - - public async setCurrentContext(context: string) { - this.kc.setCurrentContext(context) - } - - public getCurrentContext() { - return this.kc.getCurrentContext() - } - - public async getNamespaces(): Promise { - const namespaces = await this.coreV1Api.listNamespace(); - return namespaces.body.items; - } - - public async getPipelinesList() { - this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); - try { - let pipelines = await this.customObjectsApi.listNamespacedCustomObject( - 'application.kubero.dev', - 'v1alpha1', - process.env.KUBERO_NAMESPACE || 'kubero', - 'kuberopipelines' + } + + public getOperatorVersion(): string | undefined { + return this.kuberoOperatorVersion; + } + + private async loadOperatorVersion(): Promise { + const contextName = this.getCurrentContext(); + const namespace = 'kubero-operator-system'; + + if (contextName) { + const pods = await this.getPods(namespace, contextName).catch((error) => { + this.logger.debug('❌ Failed to get Operator Version'); + //this.logger.debug(error); + //return 'error'; + }); + if (pods) { + for (const pod of pods) { + if ( + pod?.metadata?.name?.startsWith( + 'kubero-operator-controller-manager', ) - return pipelines.body as IKubectlPipelineList; - } catch (error) { - //this.logger.debug(error); - this.logger.debug("❌ getPipelinesList: error getting pipelines"); + ) { + const container = pod?.spec?.containers.filter( + (c: any) => c.name == 'manager', + )[0]; + return container?.image?.split(':')[1] || 'unknown'; + } } - const pipelines = {} as IKubectlPipelineList; - pipelines.items = []; - return pipelines; + } else { + return 'error getting operator version'; + } } - - public async createPipeline(pl: IPipeline) { - this.logger.debug("create pipeline: " + pl.name); - let pipeline = new KubectlPipeline(pl); - - this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); - await this.customObjectsApi.createNamespacedCustomObject( - "application.kubero.dev", - "v1alpha1", - process.env.KUBERO_NAMESPACE || 'kubero', - "kuberopipelines", - pipeline - ).catch(error => { - this.logger.debug("❌ Error creating pipeline: " + pl.name); - //this.logger.debug(error); - }); + } + + public getContexts() { + return this.kc.getContexts(); + } + + public async setCurrentContext(context: string) { + this.kc.setCurrentContext(context); + } + + public getCurrentContext() { + return this.kc.getCurrentContext(); + } + + public async getNamespaces(): Promise { + const namespaces = await this.coreV1Api.listNamespace(); + return namespaces.body.items; + } + + public async getPipelinesList() { + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + try { + const pipelines = await this.customObjectsApi.listNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + process.env.KUBERO_NAMESPACE || 'kubero', + 'kuberopipelines', + ); + return pipelines.body as IKubectlPipelineList; + } catch (error) { + //this.logger.debug(error); + this.logger.debug('❌ getPipelinesList: error getting pipelines'); } - - public async updatePipeline(pl: IPipeline, resourceVersion: string ) { - this.logger.debug("update pipeline: " + pl.name); - let pipeline = new KubectlPipeline(pl); - pipeline.metadata.resourceVersion = resourceVersion; - - this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); - await this.customObjectsApi.replaceNamespacedCustomObject( - "application.kubero.dev", - "v1alpha1", - process.env.KUBERO_NAMESPACE || 'kubero', - "kuberopipelines", - pl.name, - pipeline - ).catch(error => { - this.logger.debug("❌ Error updating pipeline: " + pl.name); - //this.logger.debug(error); - }); + const pipelines = {} as IKubectlPipelineList; + pipelines.items = []; + return pipelines; + } + + public async createPipeline(pl: IPipeline) { + this.logger.debug('create pipeline: ' + pl.name); + const pipeline = new KubectlPipeline(pl); + + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + await this.customObjectsApi + .createNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + process.env.KUBERO_NAMESPACE || 'kubero', + 'kuberopipelines', + pipeline, + ) + .catch((error) => { + this.logger.debug('❌ Error creating pipeline: ' + pl.name); + //this.logger.debug(error); + }); + } + + public async updatePipeline(pl: IPipeline, resourceVersion: string) { + this.logger.debug('update pipeline: ' + pl.name); + const pipeline = new KubectlPipeline(pl); + pipeline.metadata.resourceVersion = resourceVersion; + + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + await this.customObjectsApi + .replaceNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + process.env.KUBERO_NAMESPACE || 'kubero', + 'kuberopipelines', + pl.name, + pipeline, + ) + .catch((error) => { + this.logger.debug('❌ Error updating pipeline: ' + pl.name); + //this.logger.debug(error); + }); + } + + public async deletePipeline(pipelineName: string) { + this.logger.debug('delete pipeline: ' + pipelineName); + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + await this.customObjectsApi + .deleteNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + process.env.KUBERO_NAMESPACE || 'kubero', + 'kuberopipelines', + pipelineName, + ) + .catch((error) => { + this.logger.debug(error); + }); + } + + public async getPipeline(pipelineName: string): Promise { + this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + const pipeline = await this.customObjectsApi + .getNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + process.env.KUBERO_NAMESPACE || 'kubero', + 'kuberopipelines', + pipelineName, + ) + .catch((error) => { + //this.logger.debug(error); + this.logger.debug('getPipeline: error getting pipeline'); + throw error; + }); + if (pipeline) { + return pipeline.body as IKubectlPipeline; + } else { + throw new Error('Pipeline not found'); + //return {} as IKubectlPipeline; } + } - public async deletePipeline(pipelineName: string) { - this.logger.debug("delete pipeline: " + pipelineName); - this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); - await this.customObjectsApi.deleteNamespacedCustomObject( - "application.kubero.dev", - "v1alpha1", - process.env.KUBERO_NAMESPACE || 'kubero', - "kuberopipelines", - pipelineName - ).catch(error => { - this.logger.debug(error); - }); - } + public async createApp(app: App, context: string) { + this.logger.debug('create app: ' + app.name); + this.kc.setCurrentContext(context); - public async getPipeline(pipelineName: string): Promise { - - this.kc.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); - let pipeline = await this.customObjectsApi.getNamespacedCustomObject( - "application.kubero.dev", - "v1alpha1", - process.env.KUBERO_NAMESPACE || 'kubero', - "kuberopipelines", - pipelineName - ).catch(error => { - //this.logger.debug(error); - this.logger.debug("getPipeline: error getting pipeline"); - throw error; - }); - if (pipeline) { - return pipeline.body as IKubectlPipeline; - } else { - throw new Error("Pipeline not found"); - //return {} as IKubectlPipeline; - } - } - - public async createApp(app: App, context: string) { - this.logger.debug("create app: " + app.name); - this.kc.setCurrentContext(context); + const appl = new KubectlApp(app); - let appl = new KubectlApp(app); + const namespace = app.pipeline + '-' + app.phase; - let namespace = app.pipeline+'-'+app.phase; - - await this.customObjectsApi.createNamespacedCustomObject( - "application.kubero.dev", - "v1alpha1", - namespace, - "kuberoapps", - appl - ).catch(error => { - console.log(error); - }) - } + await this.customObjectsApi + .createNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoapps', + appl, + ) + .catch((error) => { + console.log(error); + }); + } - public async updateApp(app: App, resourceVersion: string, context: string) { - this.logger.debug("update app: " + app.name); - this.kc.setCurrentContext(context); + public async updateApp(app: App, resourceVersion: string, context: string) { + this.logger.debug('update app: ' + app.name); + this.kc.setCurrentContext(context); - let appl = new KubectlApp(app); - appl.metadata.resourceVersion = resourceVersion; + const appl = new KubectlApp(app); + appl.metadata.resourceVersion = resourceVersion; - let namespace = app.pipeline+'-'+app.phase; + const namespace = app.pipeline + '-' + app.phase; - await this.customObjectsApi.replaceNamespacedCustomObject( + await this.customObjectsApi + .replaceNamespacedCustomObject( //await this.customObjectsApi.patchNamespacedCustomObject( // patch : https://stackoverflow.com/questions/67520468/patch-k8s-custom-resource-with-kubernetes-client-node // https://github.com/kubernetes-client/javascript/blob/master/examples/patch-example.js - "application.kubero.dev", - "v1alpha1", - namespace, - "kuberoapps", - app.name, - appl - ).catch(error => { - this.logger.debug(error); - }) + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoapps', + app.name, + appl, + ) + .catch((error) => { + this.logger.debug(error); + }); + } + + public async deleteApp( + pipelineName: string, + phaseName: string, + appName: string, + context: string, + ) { + this.logger.debug('delete app: ' + appName); + + const namespace = pipelineName + '-' + phaseName; + this.kc.setCurrentContext(context); + + await this.customObjectsApi + .deleteNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoapps', + appName, + ) + .catch((error) => { + this.logger.debug(error); + }); + } + + public async getApp( + pipelineName: string, + phaseName: string, + appName: string, + context: string, + ): Promise { + const namespace = pipelineName + '-' + phaseName; + this.kc.setCurrentContext(context); + + const app = await this.customObjectsApi + .getNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoapps', + appName, + ) + .catch((error) => { + this.logger.debug(error); + }); + + if (app) { + return app.body as IKubectlApp; + } else { + return {} as IKubectlApp; } - - public async deleteApp(pipelineName: string, phaseName: string, appName: string, context: string) { - this.logger.debug("delete app: " + appName); - - let namespace = pipelineName+'-'+phaseName; - this.kc.setCurrentContext(context); - - await this.customObjectsApi.deleteNamespacedCustomObject( - "application.kubero.dev", - "v1alpha1", - namespace, - "kuberoapps", - appName - ).catch(error => { - this.logger.debug(error); - }) + } + + public async getAppsList( + namespace: string, + context: string, + ): Promise { + this.kc.setCurrentContext(context); + try { + const appslist = await this.customObjectsApi.listNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoapps', + ); + return appslist.body as IKubectlAppList; + } catch (error) { + //this.logger.debug(error); + this.logger.debug('getAppsList: error getting apps'); } + const appslist = {} as IKubectlAppList; + appslist.items = []; + return appslist; + } + + public async restartApp( + pipelineName: string, + phaseName: string, + appName: string, + workloadType: string, + context: string, + ) { + this.logger.debug('restart app: ' + appName); + this.kc.setCurrentContext(context); + + const namespace = pipelineName + '-' + phaseName; + const deploymentName = appName + '-kuberoapp-' + workloadType; + const date = new Date(); + + // format : https://jsonpatch.com/ + const patch = [ + { + op: 'add', + path: '/spec/restartedAt', + value: { + restartedAt: date.toISOString(), + }, + }, + ]; - public async getApp(pipelineName: string, phaseName: string, appName: string, context: string): Promise { - - let namespace = pipelineName+'-'+phaseName; - this.kc.setCurrentContext(context); - - let app = await this.customObjectsApi.getNamespacedCustomObject( - "application.kubero.dev", - "v1alpha1", - namespace, - "kuberoapps", - appName - ).catch(error => { - this.logger.debug(error); - }) - - if (app) { - return app.body as IKubectlApp; - } else { - return {} as IKubectlApp; - } - } + const apiVersion = 'v1alpha1'; + const group = 'application.kubero.dev'; + const plural = 'kuberoapps'; - public async getAppsList(namespace: string, context: string): Promise { - this.kc.setCurrentContext(context); - try { - let appslist = await this.customObjectsApi.listNamespacedCustomObject( - 'application.kubero.dev', - 'v1alpha1', - namespace, - 'kuberoapps' - ) - return appslist.body as IKubectlAppList; - } catch (error) { - //this.logger.debug(error); - this.logger.debug("getAppsList: error getting apps"); - } - const appslist = {} as IKubectlAppList; - appslist.items = []; - return appslist as IKubectlAppList; - } - - public async restartApp(pipelineName: string, phaseName: string, appName: string, workloadType: string, context: string) { - this.logger.debug("restart app: " + appName); - this.kc.setCurrentContext(context); - - let namespace = pipelineName+'-'+phaseName; - let deploymentName = appName+'-kuberoapp-'+workloadType; - const date = new Date(); - - // format : https://jsonpatch.com/ - const patch = [ - { - op: 'add', - path: '/spec/restartedAt', - value: { - 'restartedAt': date.toISOString() - } - }, - ]; - - const apiVersion = "v1alpha1" - const group = "application.kubero.dev" - const plural = "kuberoapps" - - const options = { "headers": { "Content-type": 'application/json-patch+json' } }; - this.customObjectsApi.patchNamespacedCustomObject( - group, - apiVersion, - namespace, - plural, - appName, - patch, - undefined, - undefined, - undefined, - options - ).then(() => { - this.logger.debug(`Deployment ${deploymentName} in Pipeline ${namespace} updated`); - }).catch(error => { - if (error.body.message) { - this.logger.debug('ERROR: '+error.body.message); - } - this.logger.debug('ERROR: '+error); - }); + const options = { + headers: { 'Content-type': 'application/json-patch+json' }, }; - - public async getOperators() { - // TODO list operators from all clusters - let operators = { items: [] }; - try { - let response = await this.customObjectsApi.listNamespacedCustomObject( - 'operators.coreos.com', - 'v1alpha1', - 'operators', - 'clusterserviceversions' - ) - //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) { - //this.logger.debug(error); - this.logger.debug("error getting operators"); - } - - return operators.items; - } - - public async getCustomresources() { - // TODO list operators from all clusters - let operators = { items: [] }; - try { - let response = await this.customObjectsApi.listClusterCustomObject( - 'apiextensions.k8s.io', - 'v1', - 'customresourcedefinitions' - ) - operators = response.body as any // TODO : fix type. This is a hacky way to get the type to work - } catch (error: any) { - //this.logger.debug(error); - this.logger.debug("error getting customresources"); + this.customObjectsApi + .patchNamespacedCustomObject( + group, + apiVersion, + namespace, + plural, + appName, + patch, + undefined, + undefined, + undefined, + options, + ) + .then(() => { + this.logger.debug( + `Deployment ${deploymentName} in Pipeline ${namespace} updated`, + ); + }) + .catch((error) => { + if (error.body.message) { + this.logger.debug('ERROR: ' + error.body.message); } - - return operators.items; + this.logger.debug('ERROR: ' + error); + }); + } + + public async getOperators() { + // TODO list operators from all clusters + let operators = { items: [] }; + try { + const response = await this.customObjectsApi.listNamespacedCustomObject( + 'operators.coreos.com', + 'v1alpha1', + 'operators', + 'clusterserviceversions', + ); + //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) { + //this.logger.debug(error); + this.logger.debug('error getting operators'); } - public async getPods(namespace: string, context: string): Promise{ - const pods = await this.coreV1Api.listNamespacedPod(namespace); - return pods.body.items; + return operators.items; + } + + public async getCustomresources() { + // TODO list operators from all clusters + let operators = { items: [] }; + try { + const response = await this.customObjectsApi.listClusterCustomObject( + 'apiextensions.k8s.io', + 'v1', + 'customresourcedefinitions', + ); + operators = response.body as any; // TODO : fix type. This is a hacky way to get the type to work + } catch (error: any) { + //this.logger.debug(error); + this.logger.debug('error getting customresources'); } - public async createEvent(type: "Normal" | "Warning",reason: string, eventName: string, message: string) { - this.logger.debug("create event: " + eventName); - - const date = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days in the future //TODO make this configurable - const event = new CoreV1Event(); - event.apiVersion = "v1"; - event.kind = "Event"; - event.type = type; - event.message = message; - event.reason = reason; - event.metadata = { - name: eventName+'.'+Date.now().toString(), - namespace: process.env.KUBERO_NAMESPACE || 'kubero', - }; - event.involvedObject = { - kind: "Kubero", - namespace: process.env.KUBERO_NAMESPACE || 'kubero', - }; + return operators.items; + } + + public async getPods(namespace: string, context: string): Promise { + const pods = await this.coreV1Api.listNamespacedPod(namespace); + return pods.body.items; + } + + public async createEvent( + type: 'Normal' | 'Warning', + reason: string, + eventName: string, + message: string, + ) { + this.logger.debug('create event: ' + eventName); + + const date = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days in the future //TODO make this configurable + const event = new CoreV1Event(); + event.apiVersion = 'v1'; + event.kind = 'Event'; + event.type = type; + event.message = message; + event.reason = reason; + event.metadata = { + name: eventName + '.' + Date.now().toString(), + namespace: process.env.KUBERO_NAMESPACE || 'kubero', + }; + event.involvedObject = { + kind: 'Kubero', + namespace: process.env.KUBERO_NAMESPACE || 'kubero', + }; - await this.coreV1Api.createNamespacedEvent( - process.env.KUBERO_NAMESPACE || 'kubero', - event - ).catch(error => { - this.logger.debug(error); - } - )}; - - public async getEvents(namespace: string): Promise { - try { - const events = await this.coreV1Api.listNamespacedEvent(namespace); - return events.body.items; - } catch (error) { - //this.logger.debug(error); - this.logger.debug("getEvents: error getting events"); - } - const events = {} as CoreV1EventList; - events.items = []; - return events.items; + await this.coreV1Api + .createNamespacedEvent(process.env.KUBERO_NAMESPACE || 'kubero', event) + .catch((error) => { + this.logger.debug(error); + }); + } + + public async getEvents(namespace: string): Promise { + try { + const events = await this.coreV1Api.listNamespacedEvent(namespace); + return events.body.items; + } catch (error) { + //this.logger.debug(error); + this.logger.debug('getEvents: error getting events'); } + const events = {} as CoreV1EventList; + events.items = []; + return events.items; + } + + public async getPodMetrics(namespace: string, appName: string): Promise { + //TODO make this a real type + const ret: { + name: string; + namespace: string; + memory: { + unit: string; + request: number; + limit: number; + usage: number; + percentage: number; + }; + cpu: { + unit: string; + request: number; + limit: number; + usage: number; + percentage: number; + }; + }[] = []; + + try { + const metrics = await this.metricsApi.getPodMetrics(namespace); + + for (let i = 0; i < metrics.items.length; i++) { + const metric = metrics.items[i]; + + if (!metric.metadata.name.startsWith(appName + '-')) continue; + + const pod = await this.coreV1Api.readNamespacedPod( + metric.metadata.name, + namespace, + ); + const requestCPU = this.normalizeCPU( + pod.body.spec?.containers[0].resources?.requests?.cpu || '0', + ); + const requestMemory = this.normalizeMemory( + pod.body.spec?.containers[0].resources?.requests?.memory || '0', + ); + const limitsCPU = this.normalizeCPU( + pod.body.spec?.containers[0].resources?.limits?.cpu || '0', + ); + const limitsMemory = this.normalizeMemory( + pod.body.spec?.containers[0].resources?.limits?.memory || '0', + ); + const usageCPU = this.normalizeCPU(metric.containers[0].usage.cpu); + const usageMemory = this.normalizeMemory( + metric.containers[0].usage.memory, + ); + const percentageCPU = Math.round((usageCPU / limitsCPU) * 100); + const percentageMemory = Math.round((usageMemory / limitsMemory) * 100); - public async getPodMetrics(namespace: string, appName: string): Promise { //TODO make this a real type - const ret: { - name: string; - namespace: string; - memory: { - unit: string; - request: number; - limit: number; - usage: number; - percentage: number; - }; - cpu: { - unit: string; - request: number; - limit: number; - usage: number; - percentage: number; - }; - }[] = []; - - try { - const metrics = await this.metricsApi.getPodMetrics(namespace); - - for (let i = 0; i < metrics.items.length; i++) { - const metric = metrics.items[i]; - - if ( !metric.metadata.name.startsWith(appName+"-") ) continue; - - const pod = await this.coreV1Api.readNamespacedPod(metric.metadata.name, namespace); - const requestCPU = this.normalizeCPU(pod.body.spec?.containers[0].resources?.requests?.cpu || '0'); - const requestMemory = this.normalizeMemory(pod.body.spec?.containers[0].resources?.requests?.memory || '0'); - const limitsCPU = this.normalizeCPU(pod.body.spec?.containers[0].resources?.limits?.cpu || '0'); - const limitsMemory = this.normalizeMemory(pod.body.spec?.containers[0].resources?.limits?.memory || '0'); - const usageCPU = this.normalizeCPU(metric.containers[0].usage.cpu); - const usageMemory = this.normalizeMemory(metric.containers[0].usage.memory); - const percentageCPU = Math.round(usageCPU / limitsCPU * 100); - const percentageMemory = Math.round(usageMemory / limitsMemory * 100); - - /* debug caclulation *//* + /* debug caclulation */ /* console.log("resource CPU : " + requestCPU, pod.body.spec?.containers[0].resources?.requests?.cpu) console.log("limits CPU : " + limitsCPU, pod.body.spec?.containers[0].resources?.limits?.cpu) console.log("usage CPU : " + usageCPU, metric.containers[0].usage.cpu) @@ -561,735 +632,808 @@ export class KubernetesService { console.log("------------------------------------") /* end debug calculations*/ - const m = { - name: metric.metadata.name, - namespace: metric.metadata.namespace, - memory : { - unit: 'Mi', - request: requestMemory, - limit: limitsMemory, - usage: usageMemory, - percentage: percentageMemory - }, - cpu : { - unit: 'm', - request: requestCPU, - limit: limitsCPU, - usage: usageCPU, - percentage: percentageCPU - } - } - ret.push(m); - } - } catch (error: any) { - this.logger.debug('ERROR fetching metrics: '+ error); - } - - return ret; + const m = { + name: metric.metadata.name, + namespace: metric.metadata.namespace, + memory: { + unit: 'Mi', + request: requestMemory, + limit: limitsMemory, + usage: usageMemory, + percentage: percentageMemory, + }, + cpu: { + unit: 'm', + request: requestCPU, + limit: limitsCPU, + usage: usageCPU, + percentage: percentageCPU, + }, + }; + ret.push(m); + } + } catch (error: any) { + this.logger.debug('ERROR fetching metrics: ' + error); } - private normalizeCPU(resource: string): number { + return ret; + } - const regex = /([0-9]+)([a-zA-Z]*)/; - const matches = resource.match(regex); + private normalizeCPU(resource: string): number { + const regex = /([0-9]+)([a-zA-Z]*)/; + const matches = resource.match(regex); - let value = 0; - let unit = ''; - if (matches !== null && matches[1]) { - value = parseInt(matches[1]) - } - if (matches !== null && matches[2]) { - unit = matches[2] - } - - //console.log("CPU unit: " + unit + " value: " + value + " :: " +resource); - switch (unit) { - case 'm': - return value / 1; - case 'n': - return Math.round(value / 1000000); - default: - return value * 1000; - } - return 0; + let value = 0; + let unit = ''; + if (matches !== null && matches[1]) { + value = parseInt(matches[1]); + } + if (matches !== null && matches[2]) { + unit = matches[2]; } + //console.log("CPU unit: " + unit + " value: " + value + " :: " +resource); + switch (unit) { + case 'm': + return value / 1; + case 'n': + return Math.round(value / 1000000); + default: + return value * 1000; + } + return 0; + } - private normalizeMemory(resource: string): number { - - const regex = /([0-9]+)([a-zA-Z]*)/; - const matches = resource.match(regex); + private normalizeMemory(resource: string): number { + const regex = /([0-9]+)([a-zA-Z]*)/; + const matches = resource.match(regex); - let value = 0; - let unit = ''; - if (matches !== null && matches[1]) { - value = parseInt(matches[1]) - } - if (matches !== null && matches[2]) { - unit = matches[2] - } - //console.log("CPU unit: " + unit + " value: " + value + " :: " +resource); - - switch (unit) { - case 'Gi': - return value * 1000; - case 'Mi': - return value / 1; - case 'Ki': - return Math.round(value / 1000); - default: - return value; - } - return 0; + let value = 0; + let unit = ''; + if (matches !== null && matches[1]) { + value = parseInt(matches[1]); } - - public async getNodeMetrics(): Promise { - const metrics = await this.metricsApi.getNodeMetrics(); - return metrics.items; + if (matches !== null && matches[2]) { + unit = matches[2]; } - - private getPodUptimeMS(pod: V1Pod): number { - const startTime = pod.status?.startTime; - if (startTime) { - const start = new Date(startTime); - const now = new Date(); - const uptime = now.getTime() - start.getTime(); - return uptime; - } - return -1; + //console.log("CPU unit: " + unit + " value: " + value + " :: " +resource); + + switch (unit) { + case 'Gi': + return value * 1000; + case 'Mi': + return value / 1; + case 'Ki': + return Math.round(value / 1000); + default: + return value; } - - public async getPodUptimes(namespace: string): Promise { - const pods = await this.coreV1Api.listNamespacedPod(namespace); - const ret = Object(); - for (let i = 0; i < pods.body.items.length; i++) { - const pod = pods.body.items[i]; - const uptime = this.getPodUptimeMS(pod); - if (pod.metadata && pod.metadata.name) { - ret[pod.metadata.name] = { - ms: uptime, - formatted: this.formatUptime(uptime) - } - } - } - return ret; + return 0; + } + + public async getNodeMetrics(): Promise { + const metrics = await this.metricsApi.getNodeMetrics(); + return metrics.items; + } + + private getPodUptimeMS(pod: V1Pod): number { + const startTime = pod.status?.startTime; + if (startTime) { + const start = new Date(startTime); + const now = new Date(); + const uptime = now.getTime() - start.getTime(); + return uptime; } - - private formatUptime(uptime: number): string { - // 0-120s show seconds - // 2-10m show minutes and seconds - // 10-120m show minutes - // 2-48h show hours and minutes - // 2-30d show days and hours - // >30d show date - - if (uptime < 0) { - return ''; - } - if (uptime < 120000) { - const seconds = Math.floor(uptime / 1000); - return seconds + "s"; - } - if (uptime < 600000) { - const minutes = Math.floor(uptime / (1000 * 60)); - const seconds = Math.floor((uptime - (minutes * 1000 * 60)) / 1000); - if (seconds > 0) { - return minutes + "m" + seconds + "s"; - } - return minutes + "m"; - } - if (uptime < 7200000) { - const minutes = Math.floor(uptime / (1000 * 60)); - return minutes + "m"; - } - if (uptime < 172800000) { - const hours = Math.floor(uptime / (1000 * 60 * 60)); - const minutes = Math.floor((uptime - (hours * 1000 * 60 * 60)) / (1000 * 60)); - if (minutes > 0) { - return hours + "h" + minutes + "m"; - } - return hours + "h"; - } - //if (uptime < 2592000000) { - const days = Math.floor(uptime / (1000 * 60 * 60 * 24)); - const hours = Math.floor((uptime - (days * 1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - if (hours > 0) { - return days + "d" + hours + "h"; - } - return days + "d"; - //} - + return -1; + } + + public async getPodUptimes(namespace: string): Promise { + const pods = await this.coreV1Api.listNamespacedPod(namespace); + const ret = Object(); + for (let i = 0; i < pods.body.items.length; i++) { + const pod = pods.body.items[i]; + const uptime = this.getPodUptimeMS(pod); + if (pod.metadata && pod.metadata.name) { + ret[pod.metadata.name] = { + ms: uptime, + formatted: this.formatUptime(uptime), + }; + } } - - public async getStorageClasses(): Promise { - let ret: IStorageClass[] = []; - try { - const storageClasses = await this.storageV1Api.listStorageClass(); - for (let i = 0; i < storageClasses.body.items.length; i++) { - const sc = storageClasses.body.items[i]; - const storageClass = { - name: sc.metadata?.name, - provisioner: sc.provisioner, - reclaimPolicy: sc.reclaimPolicy, - volumeBindingMode: sc.volumeBindingMode, - //allowVolumeExpansion: sc.allowVolumeExpansion, - //parameters: sc.parameters - } as IStorageClass; - ret.push(storageClass); - } - } catch (error) { - this.logger.error(error); - this.logger.error('ERROR fetching storageclasses'); - } - return ret; + return ret; + } + + private formatUptime(uptime: number): string { + // 0-120s show seconds + // 2-10m show minutes and seconds + // 10-120m show minutes + // 2-48h show hours and minutes + // 2-30d show days and hours + // >30d show date + + if (uptime < 0) { + return ''; } - - public async getIngressClasses(): Promise { - // undefind = default - let ret = [{ - name: undefined - }] as Object[]; - try { - const ingressClasses = await this.networkingV1Api.listIngressClass(); - for (let i = 0; i < ingressClasses.body.items.length; i++) { - const ic = ingressClasses.body.items[i]; - const ingressClass = { - name: ic.metadata?.name, - } - ret.push(ingressClass); - } - } catch (error) { - this.logger.error(error); - this.logger.error('ERROR fetching ingressclasses'); - } - return ret; + if (uptime < 120000) { + const seconds = Math.floor(uptime / 1000); + return seconds + 's'; } - - private async deleteScanJob(namespace: string, name: string): Promise { - try { - await this.batchV1Api.deleteNamespacedJob(name, namespace); - // wait for job to be deleted - await new Promise(resolve => setTimeout(resolve, 1000)); - } catch (error) { - //console.log(error); - this.logger.error('ERROR deleting job: '+name+' ' +namespace); - } + if (uptime < 600000) { + const minutes = Math.floor(uptime / (1000 * 60)); + const seconds = Math.floor((uptime - minutes * 1000 * 60) / 1000); + if (seconds > 0) { + return minutes + 'm' + seconds + 's'; + } + return minutes + 'm'; } - - public async createScanRepoJob(namespace: string, app: string, gitrepo: string, branch: string): Promise { - await this.deleteScanJob(namespace, app+'-kuberoapp-vuln'); - const job = { - apiVersion: 'batch/v1', - kind: 'Job', - metadata: { - name: app+'-kuberoapp-vuln', - namespace: namespace, - }, - spec: { - ttlSecondsAfterFinished: 86400, - completions: 1, - template: { - metadata: { - labels: { - vulnerabilityscan: app - } - }, - spec: { - restartPolicy: 'Never', - securityContext: { - runAsUser: 1000 - }, - containers: [ - { - name: 'trivy-repo-scan', - image: "aquasec/trivy:latest", - command: [ - "trivy", - "repo", - gitrepo, - "--branch", - branch, - "-q", - "-f", - "json", - "--scanners", - "vuln,secret,config", - "--cache-dir", - "/tmp/trivy", - "--exit-code", - "0" - ], - } - ] - } - } - } - }; - try { - return await this.batchV1Api.createNamespacedJob(namespace, job); - } catch (error) { - this.logger.error(error); - this.logger.error('ERROR creating Repo scan job: '+app+' ' +namespace); - } + if (uptime < 7200000) { + const minutes = Math.floor(uptime / (1000 * 60)); + return minutes + 'm'; } - - public async createScanImageJob(namespace: string, app: string, image: string, tag: string, withCredentials: boolean): Promise { - await this.deleteScanJob(namespace, app+'-kuberoapp-vuln'); - let job = { - apiVersion: 'batch/v1', - kind: 'Job', - metadata: { - name: app+'-kuberoapp-vuln', - namespace: namespace, - }, - spec: { - ttlSecondsAfterFinished: 86400, - completions: 1, - backoffLimit: 1, - template: { - metadata: { - labels: { - vulnerabilityscan: app - } - }, - spec: { - restartPolicy: 'Never', - securityContext: { - runAsUser: 1000 - }, - containers: [ - { - name: 'trivy-repo-scan', - image: "aquasec/trivy:latest", - command: [ - "trivy", - "image", - image+":"+tag, - "-q", - "-f", - "json", - "--scanners", - "vuln", - "--cache-dir", - "/tmp/trivy", - "--exit-code", - "0" - ], - env: [] as { name: string; valueFrom: { secretKeyRef: { name: string; key: string; optional: true; }; }; }[], - } - ] - } - } - } - }; - - if (withCredentials) { - job.spec.template.spec.containers[0].env = [ - { - name: 'TRIVY_USERNAME', - valueFrom: { - secretKeyRef: { - name: 'registry-credentials', - key: 'username', - optional: true - } - } - }, - { - name: 'TRIVY_PASSWORD', - valueFrom: { - secretKeyRef: { - name: 'registry-credentials', - key: 'password', - optional: true - } - } - } - ] - } - - try { - return await this.batchV1Api.createNamespacedJob(namespace, job); - } catch (error) { - this.logger.error(error); - this.logger.error('ERROR creating Image scan job'); - } + if (uptime < 172800000) { + const hours = Math.floor(uptime / (1000 * 60 * 60)); + const minutes = Math.floor( + (uptime - hours * 1000 * 60 * 60) / (1000 * 60), + ); + if (minutes > 0) { + return hours + 'h' + minutes + 'm'; + } + return hours + 'h'; } - - public async getVulnerabilityScanLogs(namespace: string, logPod: string): Promise { - - try { - const logs = await this.coreV1Api.readNamespacedPodLog(logPod, namespace, undefined, false); - return logs.body; - } catch (error) { - this.logger.error(error); - this.logger.error('ERROR fetching scan logs'); - } + //if (uptime < 2592000000) { + const days = Math.floor(uptime / (1000 * 60 * 60 * 24)); + const hours = Math.floor( + (uptime - days * 1000 * 60 * 60 * 24) / (1000 * 60 * 60), + ); + if (hours > 0) { + return days + 'd' + hours + 'h'; } - - public async getLatestPodByLabel(namespace: string, label: string ): Promise { - - try { - const pods = await this.coreV1Api.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, label); - let latestPod: V1Pod | null = null; - for (let i = 0; i < pods.body.items.length; i++) { - const pod = pods.body.items[i]; - if (latestPod === null) { - latestPod = pod; - } else { - if ( - pod.metadata?.creationTimestamp && latestPod.metadata?.creationTimestamp && - pod.metadata?.creationTimestamp > latestPod.metadata?.creationTimestamp) { - latestPod = pod; - } - } - } - - return { - name: latestPod?.metadata?.name, - status: latestPod?.status?.phase, - startTime: latestPod?.status?.startTime, - containerStatuses: latestPod?.status?.containerStatuses - - }; - - //return latestPod?.metadata?.name - } catch (error) { - this.logger.error(error); - this.logger.error('ERROR fetching pod by label'); - } + return days + 'd'; + //} + } + + public async getStorageClasses(): Promise { + const ret: IStorageClass[] = []; + try { + const storageClasses = await this.storageV1Api.listStorageClass(); + for (let i = 0; i < storageClasses.body.items.length; i++) { + const sc = storageClasses.body.items[i]; + const storageClass = { + name: sc.metadata?.name, + provisioner: sc.provisioner, + reclaimPolicy: sc.reclaimPolicy, + volumeBindingMode: sc.volumeBindingMode, + //allowVolumeExpansion: sc.allowVolumeExpansion, + //parameters: sc.parameters + } as IStorageClass; + ret.push(storageClass); + } + } catch (error) { + this.logger.error(error); + this.logger.error('ERROR fetching storageclasses'); } - - public async deployApp(namespace: string, appName: string, tag: string) { - - let deploymentName = appName+'-kuberoapp-web'; - this.logger.error("deploy app: " + appName, ",namespace: " + namespace, ",tag: " + tag, ",deploymentName: " + deploymentName); - - // format : https://jsonpatch.com/ - const patch = [ - { - op: 'replace', - path: '/spec/image/tag', - value: tag, + return ret; + } + + public async getIngressClasses(): Promise { + // undefind = default + const ret = [ + { + name: undefined, + }, + ] as object[]; + try { + const ingressClasses = await this.networkingV1Api.listIngressClass(); + for (let i = 0; i < ingressClasses.body.items.length; i++) { + const ic = ingressClasses.body.items[i]; + const ingressClass = { + name: ic.metadata?.name, + }; + ret.push(ingressClass); + } + } catch (error) { + this.logger.error(error); + this.logger.error('ERROR fetching ingressclasses'); + } + return ret; + } + + private async deleteScanJob(namespace: string, name: string): Promise { + try { + await this.batchV1Api.deleteNamespacedJob(name, namespace); + // wait for job to be deleted + await new Promise((resolve) => setTimeout(resolve, 1000)); + } catch (error) { + //console.log(error); + this.logger.error('ERROR deleting job: ' + name + ' ' + namespace); + } + } + + public async createScanRepoJob( + namespace: string, + app: string, + gitrepo: string, + branch: string, + ): Promise { + await this.deleteScanJob(namespace, app + '-kuberoapp-vuln'); + const job = { + apiVersion: 'batch/v1', + kind: 'Job', + metadata: { + name: app + '-kuberoapp-vuln', + namespace: namespace, + }, + spec: { + ttlSecondsAfterFinished: 86400, + completions: 1, + template: { + metadata: { + labels: { + vulnerabilityscan: app, + }, + }, + spec: { + restartPolicy: 'Never', + securityContext: { + runAsUser: 1000, }, - ]; - - const apiVersion = "v1alpha1" - const group = "application.kubero.dev" - const plural = "kuberoapps" - - const options = { "headers": { "Content-type": 'application/json-patch+json' } }; - this.customObjectsApi.patchNamespacedCustomObject( - group, - apiVersion, - namespace, - plural, - appName, - patch, - undefined, - undefined, - undefined, - options - ).then(() => { - this.logger.debug(`Deployment ${deploymentName} in Pipeline ${namespace} updated`); - }).catch(error => { - if (error.body.message) { - this.logger.debug('ERROR: '+error.body.message); - } - this.logger.debug('ERROR: '+error); - }); + containers: [ + { + name: 'trivy-repo-scan', + image: 'aquasec/trivy:latest', + command: [ + 'trivy', + 'repo', + gitrepo, + '--branch', + branch, + '-q', + '-f', + 'json', + '--scanners', + 'vuln,secret,config', + '--cache-dir', + '/tmp/trivy', + '--exit-code', + '0', + ], + }, + ], + }, + }, + }, }; - - public async getAllIngress(): Promise { - const ingresses = await this.networkingV1Api.listIngressForAllNamespaces(); - return ingresses.body.items; + try { + return await this.batchV1Api.createNamespacedJob(namespace, job); + } catch (error) { + this.logger.error(error); + this.logger.error( + 'ERROR creating Repo scan job: ' + app + ' ' + namespace, + ); } + } + + public async createScanImageJob( + namespace: string, + app: string, + image: string, + tag: string, + withCredentials: boolean, + ): Promise { + await this.deleteScanJob(namespace, app + '-kuberoapp-vuln'); + const job = { + apiVersion: 'batch/v1', + kind: 'Job', + metadata: { + name: app + '-kuberoapp-vuln', + namespace: namespace, + }, + spec: { + ttlSecondsAfterFinished: 86400, + completions: 1, + backoffLimit: 1, + template: { + metadata: { + labels: { + vulnerabilityscan: app, + }, + }, + spec: { + restartPolicy: 'Never', + securityContext: { + runAsUser: 1000, + }, + containers: [ + { + name: 'trivy-repo-scan', + image: 'aquasec/trivy:latest', + command: [ + 'trivy', + 'image', + image + ':' + tag, + '-q', + '-f', + 'json', + '--scanners', + 'vuln', + '--cache-dir', + '/tmp/trivy', + '--exit-code', + '0', + ], + env: [] as { + name: string; + valueFrom: { + secretKeyRef: { name: string; key: string; optional: true }; + }; + }[], + }, + ], + }, + }, + }, + }; - public async getDomains(): Promise { - let allIngress = await this.getAllIngress() - let domains: string[] = [] - allIngress.forEach((ingress: any) => { - ingress.spec.rules.forEach((rule: any) => { - domains.push(rule.host) - }) - }) - return domains + if (withCredentials) { + job.spec.template.spec.containers[0].env = [ + { + name: 'TRIVY_USERNAME', + valueFrom: { + secretKeyRef: { + name: 'registry-credentials', + key: 'username', + optional: true, + }, + }, + }, + { + name: 'TRIVY_PASSWORD', + valueFrom: { + secretKeyRef: { + name: 'registry-credentials', + key: 'password', + optional: true, + }, + }, + }, + ]; } - public async execInContainer(namespace: string, podName: string, containerName: string, command: string, stdin: internal.PassThrough): Promise { - //const command = ['ls', '-al', '.'] - //const command = ['bash'] - //const command = "bash" - const ws = await this.exec.exec( - namespace, - podName, - containerName, - command, - process.stdout as stream.Writable, - process.stderr as stream.Writable, - stdin, - true - ); - return ws + try { + return await this.batchV1Api.createNamespacedJob(namespace, job); + } catch (error) { + this.logger.error(error); + this.logger.error('ERROR creating Image scan job'); } - - public async getKuberoConfig(namespace: string): Promise { - try { - const config = await this.customObjectsApi.getNamespacedCustomObject( - 'application.kubero.dev', - 'v1alpha1', - namespace, - 'kuberoes', - 'kubero' - ) - //console.log(config.body); - return config.body as any; - } catch (error) { - //this.logger.debug(error); - this.logger.debug("getKuberoConfig: error getting config"); - } + } + + public async getVulnerabilityScanLogs( + namespace: string, + logPod: string, + ): Promise { + try { + const logs = await this.coreV1Api.readNamespacedPodLog( + logPod, + namespace, + undefined, + false, + ); + return logs.body; + } catch (error) { + this.logger.error(error); + this.logger.error('ERROR fetching scan logs'); } - - - public async updateKuberoConfig(namespace: string, config: any) { - const patch = [ - { - op: 'replace', - path: '/spec', - value: config.spec, - }, - ]; - - const options = { "headers": { "Content-type": 'application/json-patch+json' } }; - try { - await this.customObjectsApi.patchNamespacedCustomObject( - 'application.kubero.dev', - 'v1alpha1', - namespace, - 'kuberoes', - 'kubero', - patch, - undefined, - undefined, - undefined, - options - ) - } catch (error) { - this.logger.debug(error); + } + + public async getLatestPodByLabel( + namespace: string, + label: string, + ): Promise { + try { + const pods = await this.coreV1Api.listNamespacedPod( + namespace, + undefined, + undefined, + undefined, + undefined, + label, + ); + let latestPod: V1Pod | null = null; + for (let i = 0; i < pods.body.items.length; i++) { + const pod = pods.body.items[i]; + if (latestPod === null) { + latestPod = pod; + } else { + if ( + pod.metadata?.creationTimestamp && + latestPod.metadata?.creationTimestamp && + pod.metadata?.creationTimestamp > + latestPod.metadata?.creationTimestamp + ) { + latestPod = pod; + } } + } + + return { + name: latestPod?.metadata?.name, + status: latestPod?.status?.phase, + startTime: latestPod?.status?.startTime, + containerStatuses: latestPod?.status?.containerStatuses, + }; + + //return latestPod?.metadata?.name + } catch (error) { + this.logger.error(error); + this.logger.error('ERROR fetching pod by label'); } - - public async updateKuberoSecret(namespace: string, secret: any) { - - const patch = [ - { - op: 'replace', - path: '/stringData', - value: secret, - }, - ]; - - const options = { "headers": { "Content-type": 'application/json-patch+json' } }; - try { - await this.coreV1Api.patchNamespacedSecret( - 'kubero-secrets', - namespace, - patch, - undefined, - undefined, - undefined, - undefined, - undefined, - options - ) - } catch (error) { - this.logger.debug(error); + } + + public async deployApp(namespace: string, appName: string, tag: string) { + const deploymentName = appName + '-kuberoapp-web'; + this.logger.error( + 'deploy app: ' + appName, + ',namespace: ' + namespace, + ',tag: ' + tag, + ',deploymentName: ' + deploymentName, + ); + + // format : https://jsonpatch.com/ + const patch = [ + { + op: 'replace', + path: '/spec/image/tag', + value: tag, + }, + ]; + + const apiVersion = 'v1alpha1'; + const group = 'application.kubero.dev'; + const plural = 'kuberoapps'; + + const options = { + headers: { 'Content-type': 'application/json-patch+json' }, + }; + this.customObjectsApi + .patchNamespacedCustomObject( + group, + apiVersion, + namespace, + plural, + appName, + patch, + undefined, + undefined, + undefined, + options, + ) + .then(() => { + this.logger.debug( + `Deployment ${deploymentName} in Pipeline ${namespace} updated`, + ); + }) + .catch((error) => { + if (error.body.message) { + this.logger.debug('ERROR: ' + error.body.message); } + this.logger.debug('ERROR: ' + error); + }); + } + + public async getAllIngress(): Promise { + const ingresses = await this.networkingV1Api.listIngressForAllNamespaces(); + return ingresses.body.items; + } + + public async getDomains(): Promise { + const allIngress = await this.getAllIngress(); + const domains: string[] = []; + allIngress.forEach((ingress: any) => { + ingress.spec.rules.forEach((rule: any) => { + domains.push(rule.host); + }); + }); + return domains; + } + + public async execInContainer( + namespace: string, + podName: string, + containerName: string, + command: string, + stdin: internal.PassThrough, + ): Promise { + //const command = ['ls', '-al', '.'] + //const command = ['bash'] + //const command = "bash" + const ws = await this.exec.exec( + namespace, + podName, + containerName, + command, + process.stdout as stream.Writable, + process.stderr as stream.Writable, + stdin, + true, + ); + return ws; + } + + public async getKuberoConfig(namespace: string): Promise { + try { + const config = await this.customObjectsApi.getNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoes', + 'kubero', + ); + //console.log(config.body); + return config.body as any; + } catch (error) { + //this.logger.debug(error); + this.logger.debug('getKuberoConfig: error getting config'); } - - public async createBuildJob( - namespace: string, - appName: string, - pipelineName: string, - buildstrategy: 'buildpacks' | 'dockerfile' | 'nixpacks' | 'plain', - dockerfilePath: string | undefined, - git: { - url: string, - ref: string - }, - repository: { - image: string, - tag: string - } - ): Promise { - this.logger.error('refactoring: loadJob not implemented'); - //let job = loadJob(buildstrategy) as any - let job = new Object() as any; - - const id = new Date().toISOString().replace(/[-:]/g, '').replace(/[T]/g, '-').substring(0, 13); - const name = appName + "-" + pipelineName + "-" + id; - - job.metadata.name = name.substring(0, 53); // max 53 characters allowed within kubernetes - //job.metadata.namespace = namespace; - job.metadata.labels['job-name'] = name.substring(0, 53); - job.metadata.labels['batch.kubernetes.io/job-name'] = name.substring(0, 53); - job.metadata.labels['kuberoapp'] = appName; - job.metadata.labels['kuberopipeline'] = pipelineName; - job.spec.template.metadata.labels['job-name'] = name.substring(0, 53); - job.spec.template.metadata.labels['batch.kubernetes.io/job-name'] = name.substring(0, 53); - job.spec.template.metadata.labels['kuberoapp'] = appName; - job.spec.template.metadata.labels['kuberopipeline'] = pipelineName; - job.spec.template.spec.serviceAccountName = appName+'-kuberoapp'; - job.spec.template.spec.serviceAccount = appName+'-kuberoapp'; - job.spec.template.spec.initContainers[0].env[0].value = git.url; - job.spec.template.spec.initContainers[0].env[1].value = git.ref; - job.spec.template.spec.containers[0].env[0].value = repository.image - job.spec.template.spec.containers[0].env[1].value = repository.tag+"-"+id; - job.spec.template.spec.containers[0].env[2].value = appName; - - if (buildstrategy === 'buildpacks') { - // configure build container - job.spec.template.spec.initContainers[2].args[1] = repository.image+":"+repository.tag+"-"+id; - } - if (buildstrategy === 'dockerfile') { - // configure push container - job.spec.template.spec.initContainers[1].env[1].value = repository.image+":"+repository.tag+"-"+id; - job.spec.template.spec.initContainers[1].env[2].value = dockerfilePath; - } - if (buildstrategy === 'nixpacks') { - // configure push container - job.spec.template.spec.initContainers[2].env[1].value = repository.image+":"+repository.tag+"-"+id; - job.spec.template.spec.initContainers[2].env[2].value = dockerfilePath; - } - - this.logger.log("create build job: " + job); - - try { - return await this.batchV1Api.createNamespacedJob(namespace, job); - } catch (error) { - this.logger.error(error); - this.logger.error('ERROR creating build job'); - } + } + + public async updateKuberoConfig(namespace: string, config: any) { + const patch = [ + { + op: 'replace', + path: '/spec', + value: config.spec, + }, + ]; + + const options = { + headers: { 'Content-type': 'application/json-patch+json' }, + }; + try { + await this.customObjectsApi.patchNamespacedCustomObject( + 'application.kubero.dev', + 'v1alpha1', + namespace, + 'kuberoes', + 'kubero', + patch, + undefined, + undefined, + undefined, + options, + ); + } catch (error) { + this.logger.debug(error); } - - public async deleteKuberoBuildJob(namespace: string, buildName: string) { - try { - await this.batchV1Api.deleteNamespacedJob(buildName, namespace) - } catch (error) { - this.logger.debug(error); - } + } + + public async updateKuberoSecret(namespace: string, secret: any) { + const patch = [ + { + op: 'replace', + path: '/stringData', + value: secret, + }, + ]; + + const options = { + headers: { 'Content-type': 'application/json-patch+json' }, + }; + try { + await this.coreV1Api.patchNamespacedSecret( + 'kubero-secrets', + namespace, + patch, + undefined, + undefined, + undefined, + undefined, + undefined, + options, + ); + } catch (error) { + this.logger.debug(error); + } + } + + public async createBuildJob( + namespace: string, + appName: string, + pipelineName: string, + buildstrategy: 'buildpacks' | 'dockerfile' | 'nixpacks' | 'plain', + dockerfilePath: string | undefined, + git: { + url: string; + ref: string; + }, + repository: { + image: string; + tag: string; + }, + ): Promise { + this.logger.error('refactoring: loadJob not implemented'); + //let job = loadJob(buildstrategy) as any + const job = new Object() as any; + + const id = new Date() + .toISOString() + .replace(/[-:]/g, '') + .replace(/[T]/g, '-') + .substring(0, 13); + const name = appName + '-' + pipelineName + '-' + id; + + job.metadata.name = name.substring(0, 53); // max 53 characters allowed within kubernetes + //job.metadata.namespace = namespace; + job.metadata.labels['job-name'] = name.substring(0, 53); + job.metadata.labels['batch.kubernetes.io/job-name'] = name.substring(0, 53); + job.metadata.labels['kuberoapp'] = appName; + job.metadata.labels['kuberopipeline'] = pipelineName; + job.spec.template.metadata.labels['job-name'] = name.substring(0, 53); + job.spec.template.metadata.labels['batch.kubernetes.io/job-name'] = + name.substring(0, 53); + job.spec.template.metadata.labels['kuberoapp'] = appName; + job.spec.template.metadata.labels['kuberopipeline'] = pipelineName; + job.spec.template.spec.serviceAccountName = appName + '-kuberoapp'; + job.spec.template.spec.serviceAccount = appName + '-kuberoapp'; + job.spec.template.spec.initContainers[0].env[0].value = git.url; + job.spec.template.spec.initContainers[0].env[1].value = git.ref; + job.spec.template.spec.containers[0].env[0].value = repository.image; + job.spec.template.spec.containers[0].env[1].value = + repository.tag + '-' + id; + job.spec.template.spec.containers[0].env[2].value = appName; + + if (buildstrategy === 'buildpacks') { + // configure build container + job.spec.template.spec.initContainers[2].args[1] = + repository.image + ':' + repository.tag + '-' + id; + } + if (buildstrategy === 'dockerfile') { + // configure push container + job.spec.template.spec.initContainers[1].env[1].value = + repository.image + ':' + repository.tag + '-' + id; + job.spec.template.spec.initContainers[1].env[2].value = dockerfilePath; + } + if (buildstrategy === 'nixpacks') { + // configure push container + job.spec.template.spec.initContainers[2].env[1].value = + repository.image + ':' + repository.tag + '-' + id; + job.spec.template.spec.initContainers[2].env[2].value = dockerfilePath; } + this.logger.log('create build job: ' + job); - public async getJob(namespace: string, jobName: string): Promise { - try { - const job = await this.batchV1Api.readNamespacedJob(jobName, namespace) - return job.body; - } catch (error) { - this.logger.debug(error); - this.logger.debug("getJob: error getting job"); - } + try { + return await this.batchV1Api.createNamespacedJob(namespace, job); + } catch (error) { + this.logger.error(error); + this.logger.error('ERROR creating build job'); } + } - public async getJobs(namespace: string): Promise { - try { - const jobs = await this.batchV1Api.listNamespacedJob(namespace) - return jobs.body; - } catch (error) { - this.logger.debug(error); - this.logger.debug("getJobs: error getting jobs"); - } + public async deleteKuberoBuildJob(namespace: string, buildName: string) { + try { + await this.batchV1Api.deleteNamespacedJob(buildName, namespace); + } catch (error) { + this.logger.debug(error); } - - 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() - this.logger.debug(JSON.stringify(versionInfo.body)); - return { error: null, valid: true }; - } catch (error: any) { - this.logger.error("Error validating kubeconfig: " + error); - this.logger.error(error); - return {error: error.message, valid: false}; - } + } + + public async getJob(namespace: string, jobName: string): Promise { + try { + const job = await this.batchV1Api.readNamespacedJob(jobName, namespace); + return job.body; + } catch (error) { + this.logger.debug(error); + this.logger.debug('getJob: error getting job'); + } + } + + public async getJobs(namespace: string): Promise { + try { + const jobs = await this.batchV1Api.listNamespacedJob(namespace); + return jobs.body; + } catch (error) { + this.logger.debug(error); + this.logger.debug('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); + const versionInfo = await versionApi.getCode(); + this.logger.debug(JSON.stringify(versionInfo.body)); + return { error: null, valid: true }; + } catch (error: any) { + this.logger.error('Error validating kubeconfig: ' + error); + this.logger.error(error); + return { error: error.message, valid: false }; + } + } - public updateKubectlConfig(kubeconfig: string, kubeContext: string) { - // update kubeconfig in the kubectl instance - /* + public updateKubectlConfig(kubeconfig: string, kubeContext: string) { + // update kubeconfig in the kubectl instance + /* this.kc.loadFromString(kubeconfig); this.kc.setCurrentContext(kubeContext); */ - this.initKubeConfig(); - this.logger.debug(kubeContext, this.kc.getCurrentContext()); - - this.logger.log("Kubeconfig updated"); - } - - public async checkNamespace(namespace: string): Promise { - try { - const ns = await this.coreV1Api.readNamespace(namespace); - return true; - } catch (error) { - return false; - } + this.initKubeConfig(); + this.logger.debug(kubeContext, this.kc.getCurrentContext()); + + this.logger.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 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 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) { - this.logger.error(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) { + this.logger.error(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); - this.logger.error('ERROR creating namespace'); - } + } + + 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); + this.logger.error('ERROR creating namespace'); } - -} \ No newline at end of file + } +} diff --git a/server-refactored-v3/src/logger/logger.ts b/server-refactored-v3/src/logger/logger.ts index 7f90baae..01dca439 100644 --- a/server-refactored-v3/src/logger/logger.ts +++ b/server-refactored-v3/src/logger/logger.ts @@ -1,4 +1,4 @@ -import { ConsoleLogger } from '@nestjs/common' +import { ConsoleLogger } from '@nestjs/common'; /** * A custom logger that disables all logs emitted by calling `log` method if @@ -16,12 +16,12 @@ export class CustomConsoleLogger extends ConsoleLogger { 'NestFactory', 'NestApplication', 'WebSocketsController', - ] + ]; log(_: any, context?: string): void { - context = context || '' + context = context || ''; if (!CustomConsoleLogger.contextsToIgnore.includes(context)) { - super.log.apply(this, arguments) + super.log.apply(this, arguments); } } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/logs/logs.controller.ts b/server-refactored-v3/src/logs/logs.controller.ts index df16b8a2..b1817b3b 100644 --- a/server-refactored-v3/src/logs/logs.controller.ts +++ b/server-refactored-v3/src/logs/logs.controller.ts @@ -4,10 +4,7 @@ import { LogsService } from './logs.service'; @Controller({ path: 'api/logs', version: '1' }) export class LogsController { - - constructor( - private readonly logsService: LogsService - ) {} + constructor(private readonly logsService: LogsService) {} @ApiOperation({ summary: 'Get the logs for a specific container' }) @Get('/:pipeline/:phase/:app/:container/history') @@ -15,7 +12,7 @@ export class LogsController { @Param('pipeline') pipeline: string, @Param('phase') phase: string, @Param('app') app: string, - @Param('container') container: string + @Param('container') container: string, ) { return this.logsService.getLogsHistory(pipeline, phase, app, container); } @@ -25,9 +22,8 @@ export class LogsController { async getLogsForApp( @Param('pipeline') pipeline: string, @Param('phase') phase: string, - @Param('app') app: string + @Param('app') app: string, ) { return this.logsService.startLogging(pipeline, phase, app); } - } diff --git a/server-refactored-v3/src/logs/logs.interface.ts b/server-refactored-v3/src/logs/logs.interface.ts index 5a42a4a0..9f188eb8 100644 --- a/server-refactored-v3/src/logs/logs.interface.ts +++ b/server-refactored-v3/src/logs/logs.interface.ts @@ -1,12 +1,12 @@ export interface ILoglines { - id: string, - time: number, - pipeline: string, - phase: string, - app: string, - pod: string, - podID: string, - container: string, - color: string, - log: string, -} \ No newline at end of file + id: string; + time: number; + pipeline: string; + phase: string; + app: string; + pod: string; + podID: string; + container: string; + color: string; + log: string; +} diff --git a/server-refactored-v3/src/logs/logs.module.ts b/server-refactored-v3/src/logs/logs.module.ts index cf25415c..99988fab 100644 --- a/server-refactored-v3/src/logs/logs.module.ts +++ b/server-refactored-v3/src/logs/logs.module.ts @@ -6,6 +6,6 @@ import { LogsController } from './logs.controller'; @Module({ providers: [LogsService, EventsGateway, AppsService], - controllers: [LogsController] + controllers: [LogsController], }) export class LogsModule {} diff --git a/server-refactored-v3/src/logs/logs.service.ts b/server-refactored-v3/src/logs/logs.service.ts index 766206ac..2fdd85e5 100644 --- a/server-refactored-v3/src/logs/logs.service.ts +++ b/server-refactored-v3/src/logs/logs.service.ts @@ -9,174 +9,228 @@ import { v4 as uuidv4 } from 'uuid'; @Injectable() export class LogsService { private logger = new Logger(LogsService.name); - private podLogStreams: string[]= [] + private podLogStreams: string[] = []; constructor( private kubectl: KubernetesService, private pipelinesService: PipelinesService, - private EventsGateway: EventsGateway + private EventsGateway: EventsGateway, ) {} private logcolor(str: string) { let hash = 0; for (var i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); + hash = str.charCodeAt(i) + ((hash << 5) - hash); } let color = '#'; for (var i = 0; i < 3; i++) { - var value = (hash >> (i * 8)) & 0xFF; - color += ('00' + value.toString(16)).substring(2); + const value = (hash >> (i * 8)) & 0xff; + color += ('00' + value.toString(16)).substring(2); } return color; } - public async emitLogs(pipelineName: string, phaseName: string, appName: string, podName: string, container: string) { - + public async emitLogs( + pipelineName: string, + phaseName: string, + appName: string, + podName: string, + container: string, + ) { const logStream = new Stream.PassThrough(); logStream.on('data', (chunk: any) => { - // use write rather than console.log to prevent double line feed - //process.stdout.write(chunk); - const roomname = `${pipelineName}-${phaseName}-${appName}`; - const logline = { - id: uuidv4(), - time: new Date().getTime(), - pipeline: pipelineName, - phase: phaseName, - app: appName, - pod: podName, - podID: podName.split('-')[3]+'-'+podName.split('-')[4], - container: container, - color: this.logcolor(podName), - log: chunk.toString() - }; - this.EventsGateway.sendLogline(roomname, logline); + // use write rather than console.log to prevent double line feed + //process.stdout.write(chunk); + const roomname = `${pipelineName}-${phaseName}-${appName}`; + const logline = { + id: uuidv4(), + time: new Date().getTime(), + pipeline: pipelineName, + phase: phaseName, + app: appName, + pod: podName, + podID: podName.split('-')[3] + '-' + podName.split('-')[4], + container: container, + color: this.logcolor(podName), + log: chunk.toString(), + }; + this.EventsGateway.sendLogline(roomname, logline); }); - const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); - const namespace = pipelineName+'-'+phaseName; + const contextName = await this.pipelinesService.getContext( + pipelineName, + phaseName, + ); + const namespace = pipelineName + '-' + phaseName; if (contextName) { - this.kubectl.setCurrentContext(contextName); - - if (!this.podLogStreams.includes(podName)) { - - this.kubectl.log.log(namespace, podName, container, logStream, {follow: true, tailLines: 0, pretty: false, timestamps: false}) - .then(res => { - this.logger.debug('logs started for '+podName+' '+container); - this.podLogStreams.push(podName); - }) - .catch(err => { - this.logger.debug(err); - }); - } else { - this.logger.debug('logs already running '+podName+' '+container); - } + this.kubectl.setCurrentContext(contextName); + + if (!this.podLogStreams.includes(podName)) { + this.kubectl.log + .log(namespace, podName, container, logStream, { + follow: true, + tailLines: 0, + pretty: false, + timestamps: false, + }) + .then((res) => { + this.logger.debug('logs started for ' + podName + ' ' + container); + this.podLogStreams.push(podName); + }) + .catch((err) => { + this.logger.debug(err); + }); + } else { + this.logger.debug('logs already running ' + podName + ' ' + container); + } } } - public async startLogging(pipelineName: string, phaseName: string, appName: string) { - const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); - const namespace = pipelineName+'-'+phaseName; + public async startLogging( + pipelineName: string, + phaseName: string, + appName: string, + ) { + const contextName = await this.pipelinesService.getContext( + pipelineName, + phaseName, + ); + const namespace = pipelineName + '-' + phaseName; if (contextName) { - this.kubectl.getPods(namespace, contextName).then((pods: any[]) => { - for (const pod of pods) { - - if (pod.metadata.name.startsWith(appName)) { - for (const container of pod.spec.containers) { - this.emitLogs(pipelineName, phaseName, appName, pod.metadata.name, container.name); - } - /* TODO needs some improvements since it wont load web anymore + this.kubectl.getPods(namespace, contextName).then((pods: any[]) => { + for (const pod of pods) { + if (pod.metadata.name.startsWith(appName)) { + for (const container of pod.spec.containers) { + this.emitLogs( + pipelineName, + phaseName, + appName, + pod.metadata.name, + container.name, + ); + } + /* TODO needs some improvements since it wont load web anymore for (const initcontainer of pod.spec.initContainers) { this.emitLogs(pipelineName, phaseName, appName, pod.metadata.name, initcontainer.name); } */ - } - } - }); + } + } + }); } } - - public async getLogsHistory(pipelineName: string, phaseName: string, appName: string, container: string) { - const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); - const namespace = pipelineName+'-'+phaseName; + public async getLogsHistory( + pipelineName: string, + phaseName: string, + appName: string, + container: string, + ) { + const contextName = await this.pipelinesService.getContext( + pipelineName, + phaseName, + ); + const namespace = pipelineName + '-' + phaseName; let loglines: ILoglines[] = []; if (contextName) { - const pods = await this.kubectl.getPods(namespace, contextName); - for (const pod of pods) { - - if (pod.metadata?.name?.startsWith(appName)) { - if (container == 'web') { - for (const container of pod.spec?.containers || []) { - // only fetch logs for the web container, exclude trivy and build jobs - if (!pod.metadata?.labels?.["job-name"]) { - const ll = await this.fetchLogs(namespace, pod.metadata.name, container.name, pipelineName, phaseName, appName) - loglines = loglines.concat(ll); - } - } - } else if (container == 'builder' || container == 'fetcher') { - const ll = await this.fetchLogs(namespace, pod.metadata.name, "kuberoapp-"+container, pipelineName, phaseName, appName) - loglines = loglines.concat(ll); - } else { - // leace the loglines empty - console.log('unknown container: '+container); - } + const pods = await this.kubectl.getPods(namespace, contextName); + for (const pod of pods) { + if (pod.metadata?.name?.startsWith(appName)) { + if (container == 'web') { + for (const container of pod.spec?.containers || []) { + // only fetch logs for the web container, exclude trivy and build jobs + if (!pod.metadata?.labels?.['job-name']) { + const ll = await this.fetchLogs( + namespace, + pod.metadata.name, + container.name, + pipelineName, + phaseName, + appName, + ); + loglines = loglines.concat(ll); + } } + } else if (container == 'builder' || container == 'fetcher') { + const ll = await this.fetchLogs( + namespace, + pod.metadata.name, + 'kuberoapp-' + container, + pipelineName, + phaseName, + appName, + ); + loglines = loglines.concat(ll); + } else { + // leace the loglines empty + console.log('unknown container: ' + container); + } } + } } return loglines; } - public async fetchLogs(namespace: string, podName: string, containerName: string, pipelineName: string, phaseName: string, appName: string): Promise { - let loglines: ILoglines[] = []; + public async fetchLogs( + namespace: string, + podName: string, + containerName: string, + pipelineName: string, + phaseName: string, + appName: string, + ): Promise { + const loglines: ILoglines[] = []; const logStream = new Stream.PassThrough(); - let logs: String = ''; + let logs: string = ''; logStream.on('data', (chunk: any) => { - //console.log(chunk.toString()); - logs += chunk.toString(); + //console.log(chunk.toString()); + logs += chunk.toString(); }); try { - await this.kubectl.log.log(namespace, podName, containerName, logStream, {follow: false, tailLines: 80, pretty: false, timestamps: true}) + await this.kubectl.log.log(namespace, podName, containerName, logStream, { + follow: false, + tailLines: 80, + pretty: false, + timestamps: true, + }); } catch (error) { - console.log("error getting logs for "+podName+" "+containerName); - return []; + console.log('error getting logs for ' + podName + ' ' + containerName); + return []; } - + // sleep for 1 second to wait for all logs to be collected - await new Promise(r => setTimeout(r, 300)); + await new Promise((r) => setTimeout(r, 300)); // split loglines into array const loglinesArray = logs.split('\n').reverse(); for (const logline of loglinesArray) { - if (logline.length > 0) { - // split after first whitespace - const loglineArray = logline.split(/(?<=^\S+)\s/); - const loglineDate = new Date(loglineArray[0]); - const loglineText = loglineArray[1]; - - loglines.push({ - id: uuidv4(), - time: loglineDate.getTime(), - pipeline: pipelineName, - phase: phaseName, - app: appName, - pod: podName, - podID: podName.split('-')[3]+'-'+podName.split('-')[4], - container: containerName, - color: this.logcolor(podName), - log: loglineText - }); - } + if (logline.length > 0) { + // split after first whitespace + const loglineArray = logline.split(/(?<=^\S+)\s/); + const loglineDate = new Date(loglineArray[0]); + const loglineText = loglineArray[1]; + + loglines.push({ + id: uuidv4(), + time: loglineDate.getTime(), + pipeline: pipelineName, + phase: phaseName, + app: appName, + pod: podName, + podID: podName.split('-')[3] + '-' + podName.split('-')[4], + container: containerName, + color: this.logcolor(podName), + log: loglineText, + }); + } } return loglines; } - - - } diff --git a/server-refactored-v3/src/main.ts b/server-refactored-v3/src/main.ts index 1f25e363..06c21342 100644 --- a/server-refactored-v3/src/main.ts +++ b/server-refactored-v3/src/main.ts @@ -1,5 +1,5 @@ import { NestFactory } from '@nestjs/core'; -import { Logger, } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import { CustomConsoleLogger } from './logger/logger'; import { LogLevel } from '@nestjs/common/services/logger.service'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; @@ -11,8 +11,14 @@ import * as dotenv from 'dotenv'; dotenv.config(); async function bootstrap() { - - const logLevels = process.env.LOGLEVELS?.split(',') ?? ['log', 'fatal', 'error', 'warn', 'debug', 'verbose']; + const logLevels = process.env.LOGLEVELS?.split(',') ?? [ + 'log', + 'fatal', + 'error', + 'warn', + 'debug', + 'verbose', + ]; Logger.log(`Log levels: ${logLevels}`, 'Bootstrap'); const app = await NestFactory.create(AppModule, { @@ -23,12 +29,13 @@ async function bootstrap() { cors: true, }); - app.use(helmet({ - contentSecurityPolicy: false, - strictTransportSecurity: false, - crossOriginOpenerPolicy: false, - crossOriginEmbedderPolicy: false, - /* suggested settings. Requires further testing. + app.use( + helmet({ + contentSecurityPolicy: false, + strictTransportSecurity: false, + crossOriginOpenerPolicy: false, + crossOriginEmbedderPolicy: false, + /* suggested settings. Requires further testing. contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], @@ -48,11 +55,14 @@ async function bootstrap() { crossOriginOpenerPolicy: { policy: 'same-origin' }, crossOriginEmbedderPolicy: { policy: 'require-corp' }, */ - })); + }), + ); const config = new DocumentBuilder() .setTitle('Kubero') - .setDescription('Kubero is a web-based tool deploy applications on a Kubernetes clusters. It provides a simple and intuitive interface to manage your clusters, applications, and pipelines.') + .setDescription( + 'Kubero is a web-based tool deploy applications on a Kubernetes clusters. It provides a simple and intuitive interface to manage your clusters, applications, and pipelines.', + ) .setVersion('3.0') .addServer('/', 'Local (default)') //.addServer('http://localhost:2000/', 'Local') @@ -84,9 +94,11 @@ async function bootstrap() { const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, documentFactory); - await app.listen(process.env.PORT ?? 2000); // Use port 2000 for compatibility with kubero v2 - Logger.log(`⚡️[server]: Server is running at: ${await app.getUrl()}`, 'Bootstrap'); + Logger.log( + `⚡️[server]: Server is running at: ${await app.getUrl()}`, + 'Bootstrap', + ); } bootstrap(); diff --git a/server-refactored-v3/src/metrics/metrics.controller.ts b/server-refactored-v3/src/metrics/metrics.controller.ts index c80dbe8a..369dc3b1 100644 --- a/server-refactored-v3/src/metrics/metrics.controller.ts +++ b/server-refactored-v3/src/metrics/metrics.controller.ts @@ -4,9 +4,7 @@ import { MetricsService } from './metrics.service'; @Controller({ path: 'api/metrics', version: '1' }) export class MetricsController { - constructor( - private metricsService: MetricsService, - ) {} + constructor(private metricsService: MetricsService) {} @ApiOperation({ summary: 'Get metrics for a specific app' }) @Get('/resources/:pipeline/:phase/:app') diff --git a/server-refactored-v3/src/metrics/metrics.interface.ts b/server-refactored-v3/src/metrics/metrics.interface.ts index 10a8dbd1..6e9bd333 100644 --- a/server-refactored-v3/src/metrics/metrics.interface.ts +++ b/server-refactored-v3/src/metrics/metrics.interface.ts @@ -1,31 +1,31 @@ export interface MetricsOptions { - enabled: boolean, - endpoint: string, + enabled: boolean; + endpoint: string; } export interface PrometheusQuery { - scale: '2h' | '24h' | '7d', - pipeline: string, - phase: string, - app?: string, - host?: string, - calc?: 'rate' | 'increase' + scale: '2h' | '24h' | '7d'; + pipeline: string; + phase: string; + app?: string; + host?: string; + calc?: 'rate' | 'increase'; } export interface IMetric { - name: string, - metric: any, + name: string; + metric: any; data: { - x: Date, - y: number - }[] + x: Date; + y: number; + }[]; } export type Rule = { - alert: any, - duration: number, - health: string, - labels: any, - name: string, - query: string, - alerting: boolean, -} \ No newline at end of file + alert: any; + duration: number; + health: string; + labels: any; + name: string; + query: string; + alerting: boolean; +}; diff --git a/server-refactored-v3/src/metrics/metrics.module.ts b/server-refactored-v3/src/metrics/metrics.module.ts index 1afeeec2..ac238534 100644 --- a/server-refactored-v3/src/metrics/metrics.module.ts +++ b/server-refactored-v3/src/metrics/metrics.module.ts @@ -5,6 +5,6 @@ import { KubernetesModule } from '../kubernetes/kubernetes.module'; @Module({ controllers: [MetricsController], - providers: [MetricsService, KubernetesModule] + providers: [MetricsService, KubernetesModule], }) export class MetricsModule {} diff --git a/server-refactored-v3/src/metrics/metrics.service.ts b/server-refactored-v3/src/metrics/metrics.service.ts index 032f8799..56d2d89a 100644 --- a/server-refactored-v3/src/metrics/metrics.service.ts +++ b/server-refactored-v3/src/metrics/metrics.service.ts @@ -1,71 +1,88 @@ import { Injectable, Logger } from '@nestjs/common'; -import { MetricsOptions, IMetric, PrometheusQuery, Rule } from './metrics.interface'; +import { + MetricsOptions, + IMetric, + PrometheusQuery, + Rule, +} from './metrics.interface'; import { KubernetesService } from '../kubernetes/kubernetes.service'; -import { PrometheusDriver, PrometheusQueryDate, QueryResult, RuleGroup } from 'prometheus-query'; +import { + PrometheusDriver, + PrometheusQueryDate, + QueryResult, + RuleGroup, +} from 'prometheus-query'; @Injectable() export class MetricsService { - private prom: PrometheusDriver + private prom: PrometheusDriver; private status: boolean = false; constructor( - //options: MetricsOptions - private kubectl: KubernetesService + //options: MetricsOptions + private kubectl: KubernetesService, ) { - //TODO: Migration -> Load options from settings or config - const options = { - enabled: true, - endpoint: 'http://prometheus.localhost' - } as MetricsOptions; - - this.prom = new PrometheusDriver({ - endpoint: options.endpoint, - preferPost: false, - withCredentials: false, - }); - - if (!options.enabled) { - Logger.log('☑️ Feature: Prometheus Metrics not enabled ...', 'Feature'); - this.status = false; - return - } + //TODO: Migration -> Load options from settings or config + const options = { + enabled: true, + endpoint: 'http://prometheus.localhost', + } as MetricsOptions; - this.prom.status().then((status) => { - Logger.log('✅ Feature: Prometheus Metrics initialized::: '+ options.endpoint, 'Feature'); - this.status = true; - }).catch((error) => { - Logger.log('❌ Feature: Prometheus not accesible ...', 'Feature'); - this.status = false; - }) + this.prom = new PrometheusDriver({ + endpoint: options.endpoint, + preferPost: false, + withCredentials: false, + }); + if (!options.enabled) { + Logger.log('☑️ Feature: Prometheus Metrics not enabled ...', 'Feature'); + this.status = false; + return; + } + + this.prom + .status() + .then((status) => { + Logger.log( + '✅ Feature: Prometheus Metrics initialized::: ' + options.endpoint, + 'Feature', + ); + this.status = true; + }) + .catch((error) => { + Logger.log('❌ Feature: Prometheus not accesible ...', 'Feature'); + this.status = false; + }); } public async getStatus(): Promise { - try { - const status = await this.prom.status(); - - if (status === undefined || status === null || status === false) { - return false; - } else { - return true; - } - } catch (error) { - return false; + try { + const status = await this.prom.status(); + + if (status === undefined || status === null || status === false) { + return false; + } else { + return true; } + } catch (error) { + return false; + } } - public async getLongTermMetrics(query: string): Promise { - let result: QueryResult | undefined; - try { - result = await this.prom.instantQuery(query); - } catch (error) { - console.log(error); - console.log("query:", query); - console.log(this.prom); - } - return result + public async getLongTermMetrics( + query: string, + ): Promise { + let result: QueryResult | undefined; + try { + result = await this.prom.instantQuery(query); + } catch (error) { + console.log(error); + console.log('query:', query); + console.log(this.prom); + } + return result; - /* Manual Query + /* Manual Query const res = await axios.get('http://prometheus.localhost/api/v1/query', { params: { query: query @@ -80,287 +97,321 @@ export class MetricsService { */ } - public async queryMetrics(metric:string, q: PrometheusQuery): Promise { - const query = `${metric}{namespace="${q.pipeline}-${q.phase}", container=~"kuberoapp-web|kuberoapp-worker"}`; - //console.log(query); - const { end, start, step, vector } = this.getStepsAndStart(q.scale); - let result: QueryResult | undefined; - try { - result = await this.prom.rangeQuery(query, start, end, step); - } catch (error) { - console.log(error); - console.log(q); - console.log("query:", query); - console.log(end, start, step ); - console.log(this.prom); - } - return result; + public async queryMetrics( + metric: string, + q: PrometheusQuery, + ): Promise { + const query = `${metric}{namespace="${q.pipeline}-${q.phase}", container=~"kuberoapp-web|kuberoapp-worker"}`; + //console.log(query); + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + let result: QueryResult | undefined; + try { + result = await this.prom.rangeQuery(query, start, end, step); + } catch (error) { + console.log(error); + console.log(q); + console.log('query:', query); + console.log(end, start, step); + console.log(this.prom); + } + return result; } public async getMemoryMetrics(q: PrometheusQuery): Promise { - - let resp = [] as IMetric[]; - let metrics: QueryResult - try { - const res = await this.queryMetrics('container_memory_rss', q); - if (res === undefined) { - throw new Error("no metrics found") - } else { - metrics = res; - } - } catch (error) { - console.log("error fetching load metrics") - throw error - } - for (let i = 0; i < metrics.result.length; i++) { - const data = metrics.result[i].values.map((v: any) => { - return [Date.parse(v.time), v.value / 1000000] - }); - resp.push({ - name: metrics.result[i].metric.labels.pod, - metric: metrics.result[i].metric, - data: data - }); + const resp = [] as IMetric[]; + let metrics: QueryResult; + try { + const res = await this.queryMetrics('container_memory_rss', q); + if (res === undefined) { + throw new Error('no metrics found'); + } else { + metrics = res; } + } catch (error) { + console.log('error fetching load metrics'); + throw error; + } + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value / 1000000]; + }); + resp.push({ + name: metrics.result[i].metric.labels.pod, + metric: metrics.result[i].metric, + data: data, + }); + } - return resp; + return resp; } public async getLoadMetrics(q: PrometheusQuery): Promise { - let resp = [] as IMetric[]; - let metrics: QueryResult - try { - const res = await this.queryMetrics('container_cpu_load_average_10s', q); - if (res === undefined) { - throw new Error("no metrics found") - } else { - metrics = res; - } - } catch (error) { - console.log("error fetching load metrics") - throw error - } - for (let i = 0; i < metrics.result.length; i++) { - const data = metrics.result[i].values.map((v: any) => { - return [Date.parse(v.time), v.value] - }); - resp.push({ - name: metrics.result[i].metric.labels.pod, - metric: metrics.result[i].metric, - data: data - }); + const resp = [] as IMetric[]; + let metrics: QueryResult; + try { + const res = await this.queryMetrics('container_cpu_load_average_10s', q); + if (res === undefined) { + throw new Error('no metrics found'); + } else { + metrics = res; } + } catch (error) { + console.log('error fetching load metrics'); + throw error; + } + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value]; + }); + resp.push({ + name: metrics.result[i].metric.labels.pod, + metric: metrics.result[i].metric, + data: data, + }); + } - return resp; + return resp; } - private getStepsAndStart(scale: string): { end: Date, start: number, step: number, vector: string} { - const end = new Date(); - let start = new Date().getTime() - 24 * 60 * 60 * 1000 - let step = 60 * 10 - let vector = '5m' - switch (scale) { - case '2h': - start = new Date().getTime() - 2 * 60 * 60 * 1000 - step = 48 // 48 seconds - vector = '5m' - break - case '24h': - start = new Date().getTime() - 24 * 60 * 60 * 1000 - step = 60 * 10 // 10 minutes - vector = '10m' - break - case '7d': - start = new Date().getTime() - 7 * 24 * 60 * 60 * 1000 - step = 60 * 120 // 700 minutes - vector = '20m' - break - } + private getStepsAndStart(scale: string): { + end: Date; + start: number; + step: number; + vector: string; + } { + const end = new Date(); + let start = new Date().getTime() - 24 * 60 * 60 * 1000; + let step = 60 * 10; + let vector = '5m'; + switch (scale) { + case '2h': + start = new Date().getTime() - 2 * 60 * 60 * 1000; + step = 48; // 48 seconds + vector = '5m'; + break; + case '24h': + start = new Date().getTime() - 24 * 60 * 60 * 1000; + step = 60 * 10; // 10 minutes + vector = '10m'; + break; + case '7d': + start = new Date().getTime() - 7 * 24 * 60 * 60 * 1000; + step = 60 * 120; // 700 minutes + vector = '20m'; + break; + } - return { - end: end, - start: start, - step: step, - vector: vector - } + return { + end: end, + start: start, + step: step, + vector: vector, + }; } public async getCPUMetrics(q: PrometheusQuery): Promise { - let resp = [] as IMetric[]; - let metrics: QueryResult - - const { end, start, step, vector } = this.getStepsAndStart(q.scale); - // rate(nginx_ingress_controller_requests{namespace="asdf-production", host="a.a.localhost"}[10m]) - const query = `${q.calc}(container_cpu_usage_seconds_total{namespace="${q.pipeline}-${q.phase}", container=~"kuberoapp-web|kuberoapp-worker"}[${vector}])`; - //console.log(query); - try { - metrics = await this.prom.rangeQuery(query, start, end, step); - for (let i = 0; i < metrics.result.length; i++) { - const data = metrics.result[i].values.map((v: any) => { - return [Date.parse(v.time), v.value] - }); - resp.push({ - name: metrics.result[i].metric.labels.pod, - metric: metrics.result[i].metric, - data: data - }); - } - } catch (error) { - console.log(error); - console.log(q); - console.log("query:", query); - console.log(end, start, step ); - console.log(this.prom); + const resp = [] as IMetric[]; + let metrics: QueryResult; + + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // rate(nginx_ingress_controller_requests{namespace="asdf-production", host="a.a.localhost"}[10m]) + const query = `${q.calc}(container_cpu_usage_seconds_total{namespace="${q.pipeline}-${q.phase}", container=~"kuberoapp-web|kuberoapp-worker"}[${vector}])`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value]; + }); + resp.push({ + name: metrics.result[i].metric.labels.pod, + metric: metrics.result[i].metric, + data: data, + }); } - return resp; + } catch (error) { + console.log(error); + console.log(q); + console.log('query:', query); + console.log(end, start, step); + console.log(this.prom); + } + return resp; } - public async getHttpStatusCodesMetrics(q: PrometheusQuery): Promise { - let resp = [] as IMetric[]; - let metrics: QueryResult - - const { end, start, step, vector } = this.getStepsAndStart(q.scale); - // rate(nginx_ingress_controller_requests{namespace="asdf-production", host="a.a.localhost"}[10m]) - const query = `${q.calc}(nginx_ingress_controller_requests{namespace="${q.pipeline}-${q.phase}", host="${q.host}"}[${vector}])`; - //console.log(query); - try { - metrics = await this.prom.rangeQuery(query, start, end, step); - for (let i = 0; i < metrics.result.length; i++) { - const data = metrics.result[i].values.map((v: any) => { - return [Date.parse(v.time), v.value] - }); - resp.push({ - name: metrics.result[i].metric.labels.status, - metric: metrics.result[i].metric, - data: data - }); - } - } catch (error) { - console.log(error); - console.log(q); - console.log("query:", query); - console.log(end, start, step ); - console.log(this.prom); + public async getHttpStatusCodesMetrics( + q: PrometheusQuery, + ): Promise { + const resp = [] as IMetric[]; + let metrics: QueryResult; + + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // rate(nginx_ingress_controller_requests{namespace="asdf-production", host="a.a.localhost"}[10m]) + const query = `${q.calc}(nginx_ingress_controller_requests{namespace="${q.pipeline}-${q.phase}", host="${q.host}"}[${vector}])`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value]; + }); + resp.push({ + name: metrics.result[i].metric.labels.status, + metric: metrics.result[i].metric, + data: data, + }); } - return resp; + } catch (error) { + console.log(error); + console.log(q); + console.log('query:', query); + console.log(end, start, step); + console.log(this.prom); + } + return resp; } + public async getHttpResponseTimeMetrics( + q: PrometheusQuery, + ): Promise { + const resp = [] as IMetric[]; + let metrics: QueryResult; - public async getHttpResponseTimeMetrics(q: PrometheusQuery): Promise { - let resp = [] as IMetric[]; - let metrics: QueryResult - - const { end, start, step, vector } = this.getStepsAndStart(q.scale); - // rate(nginx_ingress_controller_response_duration_seconds_count{namespace="asdf-production", host="a.a.localhost",status="200"}[10m]) //in ms - const query = `${q.calc}(nginx_ingress_controller_response_duration_seconds_count{namespace="${q.pipeline}-${q.phase}", host="${q.host}", status="200"}[${vector}])`; - //console.log(query); - try { - metrics = await this.prom.rangeQuery(query, start, end, step); - for (let i = 0; i < metrics.result.length; i++) { - const data = metrics.result[i].values.map((v: any) => { - return [Date.parse(v.time), v.value/1000] - }); - resp.push({ - name: metrics.result[i].metric.labels.status, - metric: metrics.result[i].metric, - data: data - }); - } - } catch (error) { - console.log(error); - console.log(q); - console.log("query:", query); - console.log(end, start, step ); - console.log(this.prom); + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // rate(nginx_ingress_controller_response_duration_seconds_count{namespace="asdf-production", host="a.a.localhost",status="200"}[10m]) //in ms + const query = `${q.calc}(nginx_ingress_controller_response_duration_seconds_count{namespace="${q.pipeline}-${q.phase}", host="${q.host}", status="200"}[${vector}])`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value / 1000]; + }); + resp.push({ + name: metrics.result[i].metric.labels.status, + metric: metrics.result[i].metric, + data: data, + }); } - return resp; + } catch (error) { + console.log(error); + console.log(q); + console.log('query:', query); + console.log(end, start, step); + console.log(this.prom); + } + return resp; } - - public async getHttpResponseTrafficMetrics(q: PrometheusQuery): Promise { - let resp = [] as IMetric[]; - let metrics: QueryResult - - const { end, start, step, vector } = this.getStepsAndStart(q.scale); - // sum(rate(nginx_ingress_controller_response_size_sum{namespace="asdf-production", host="a.a.localhost"}[10m])) - const query = `sum(${q.calc}(nginx_ingress_controller_response_size_sum{namespace="${q.pipeline}-${q.phase}", host="${q.host}"}[${vector}]))`; - //console.log(query); - try { - metrics = await this.prom.rangeQuery(query, start, end, step); - for (let i = 0; i < metrics.result.length; i++) { - const data = metrics.result[i].values.map((v: any) => { - return [Date.parse(v.time), v.value/1000] - }); - resp.push({ - name: metrics.result[i].metric.labels.status, - metric: metrics.result[i].metric, - data: data - }); - } - } catch (error) { - console.log(error); - console.log(q); - console.log("query:", query); - console.log(end, start, step ); - console.log(this.prom); + + public async getHttpResponseTrafficMetrics( + q: PrometheusQuery, + ): Promise { + const resp = [] as IMetric[]; + let metrics: QueryResult; + + const { end, start, step, vector } = this.getStepsAndStart(q.scale); + // sum(rate(nginx_ingress_controller_response_size_sum{namespace="asdf-production", host="a.a.localhost"}[10m])) + const query = `sum(${q.calc}(nginx_ingress_controller_response_size_sum{namespace="${q.pipeline}-${q.phase}", host="${q.host}"}[${vector}]))`; + //console.log(query); + try { + metrics = await this.prom.rangeQuery(query, start, end, step); + for (let i = 0; i < metrics.result.length; i++) { + const data = metrics.result[i].values.map((v: any) => { + return [Date.parse(v.time), v.value / 1000]; + }); + resp.push({ + name: metrics.result[i].metric.labels.status, + metric: metrics.result[i].metric, + data: data, + }); } - return resp; + } catch (error) { + console.log(error); + console.log(q); + console.log('query:', query); + console.log(end, start, step); + console.log(this.prom); + } + return resp; } - public async getRules(q: {app: string, phase: string, pipeline: string}): Promise { - let rules: RuleGroup[] = []; - try { - rules = await this.prom.rules(); - } catch (error) { - console.log("error fetching rules") - } + public async getRules(q: { + app: string; + phase: string; + pipeline: string; + }): Promise { + let rules: RuleGroup[] = []; + try { + rules = await this.prom.rules(); + } catch (error) { + console.log('error fetching rules'); + } - let ruleslist: Rule[] = []; - - // filter for dedicated app - for (let i = 0; i < rules.length; i++) { - for (let j = 0; j < rules[i].rules.length; j++) { - // remove not matching alerts - rules[i].rules[j].alerts = rules[i].rules[j].alerts.filter((a: any) => { - console.log("a.labels.namespace: "+a.labels.namespace+" == q.pipeline: "+q.pipeline+"-"+q.phase) - console.log("a.labels.service: "+a.labels.service+" q.app: "+q.app+"-kuberoapp"); - return a.labels.namespace === q.pipeline+"-"+q.phase && ( - a.labels.service === q.app+"-kuberoapp" || - a.labels.deployment?.startsWith(q.app+"-kuberoapp") || - a.labels.replicaset?.startsWith(q.app+"-kuberoapp") || - a.labels.statefulset === q.app+"-kuberoapp" || - a.labels.daemonset === q.app+"-kuberoapp" || - a.labels.pod === q.app+"-kuberoapp" || - a.labels.container === q.app+"-kuberoapp" || - a.labels.job === q.app+"-kuberoapp" - ) - }); - - let r: Rule = { - alert: rules[i].rules[j].alerts[0], - duration: rules[i].rules[j].duration || 0, - health: rules[i].rules[j].health || '', - labels: rules[i].rules[j].labels || {}, - name: rules[i].rules[j].name || '', - query: rules[i].rules[j].query || '', - alerting: rules[i].rules[j].alerts.length > 0 ? true : false, - }; - - if (rules[i].rules[j].type === 'alerting') { - ruleslist.push(r); - } - } + const ruleslist: Rule[] = []; + + // filter for dedicated app + for (let i = 0; i < rules.length; i++) { + for (let j = 0; j < rules[i].rules.length; j++) { + // remove not matching alerts + rules[i].rules[j].alerts = rules[i].rules[j].alerts.filter((a: any) => { + console.log( + 'a.labels.namespace: ' + + a.labels.namespace + + ' == q.pipeline: ' + + q.pipeline + + '-' + + q.phase, + ); + console.log( + 'a.labels.service: ' + + a.labels.service + + ' q.app: ' + + q.app + + '-kuberoapp', + ); + return ( + a.labels.namespace === q.pipeline + '-' + q.phase && + (a.labels.service === q.app + '-kuberoapp' || + a.labels.deployment?.startsWith(q.app + '-kuberoapp') || + a.labels.replicaset?.startsWith(q.app + '-kuberoapp') || + a.labels.statefulset === q.app + '-kuberoapp' || + a.labels.daemonset === q.app + '-kuberoapp' || + a.labels.pod === q.app + '-kuberoapp' || + a.labels.container === q.app + '-kuberoapp' || + a.labels.job === q.app + '-kuberoapp') + ); + }); + + const r: Rule = { + alert: rules[i].rules[j].alerts[0], + duration: rules[i].rules[j].duration || 0, + health: rules[i].rules[j].health || '', + labels: rules[i].rules[j].labels || {}, + name: rules[i].rules[j].name || '', + query: rules[i].rules[j].query || '', + alerting: rules[i].rules[j].alerts.length > 0 ? true : false, + }; + + if (rules[i].rules[j].type === 'alerting') { + ruleslist.push(r); + } } + } - return ruleslist; + return ruleslist; } - public getPodMetrics(pipelineName: string, phaseName: string, appName: string) { - const namespace = pipelineName+'-'+phaseName; + public getPodMetrics( + pipelineName: string, + phaseName: string, + appName: string, + ) { + const namespace = pipelineName + '-' + phaseName; return this.kubectl.getPodMetrics(namespace, appName); } public getUptimes(pipelineName: string, phaseName: string) { - const namespace = pipelineName+'-'+phaseName; + const namespace = pipelineName + '-' + phaseName; return this.kubectl.getPodUptimes(namespace); } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/notifications/notifications.interface.ts b/server-refactored-v3/src/notifications/notifications.interface.ts index 8686fd7b..9eeec561 100644 --- a/server-refactored-v3/src/notifications/notifications.interface.ts +++ b/server-refactored-v3/src/notifications/notifications.interface.ts @@ -1,22 +1,38 @@ export interface INotification { - name: string, - user: string, - resource: "system" | "app" | "pipeline" | "phase" | "namespace" | "build" | "addon" | "settings" | "user" | "events" | "security" | "templates" | "config" | "addons" | "kubernetes" | "unknown", - action: string, - severity: "normal" | "info" | "warning" | "critical" | "error" | "unknown", - message: string, - phaseName: string, - pipelineName: string, - appName: string, - data?: any + name: string; + user: string; + resource: + | 'system' + | 'app' + | 'pipeline' + | 'phase' + | 'namespace' + | 'build' + | 'addon' + | 'settings' + | 'user' + | 'events' + | 'security' + | 'templates' + | 'config' + | 'addons' + | 'kubernetes' + | 'unknown'; + action: string; + severity: 'normal' | 'info' | 'warning' | 'critical' | 'error' | 'unknown'; + message: string; + phaseName: string; + pipelineName: string; + appName: string; + data?: any; } -export interface INotificationConfig{ +export interface INotificationConfig { enabled: boolean; name: string; - type: 'slack' | 'webhook' | 'discord', - pipelines: string[], - events: string[], + type: 'slack' | 'webhook' | 'discord'; + pipelines: string[]; + events: string[]; config: INotificationSlack | INotificationWebhook | INotificationDiscord; } diff --git a/server-refactored-v3/src/notifications/notifications.module.ts b/server-refactored-v3/src/notifications/notifications.module.ts index f8568872..bfdf5503 100644 --- a/server-refactored-v3/src/notifications/notifications.module.ts +++ b/server-refactored-v3/src/notifications/notifications.module.ts @@ -6,7 +6,12 @@ import { KubernetesModule } from '../kubernetes/kubernetes.module'; @Global() @Module({ - providers: [NotificationsService, EventsGateway, AuditModule, KubernetesModule], + providers: [ + NotificationsService, + EventsGateway, + AuditModule, + KubernetesModule, + ], exports: [NotificationsService], }) export class NotificationsModule {} diff --git a/server-refactored-v3/src/notifications/notifications.service.ts b/server-refactored-v3/src/notifications/notifications.service.ts index 5b5cd190..0d3011cb 100644 --- a/server-refactored-v3/src/notifications/notifications.service.ts +++ b/server-refactored-v3/src/notifications/notifications.service.ts @@ -1,14 +1,19 @@ import { Injectable, Logger } from '@nestjs/common'; import { AuditService } from 'src/audit/audit.service'; import { KubernetesService } from '../kubernetes/kubernetes.service'; -import { INotificationConfig, INotification, INotificationSlack, INotificationWebhook, INotificationDiscord } from './notifications.interface'; +import { + INotificationConfig, + INotification, + INotificationSlack, + INotificationWebhook, + INotificationDiscord, +} from './notifications.interface'; import { EventsGateway } from '../events/events.gateway'; import { IKuberoConfig } from '../settings/settings.interface'; import fetch from 'node-fetch'; @Injectable() export class NotificationsService { - //public kubectl: Kubectl; //private audit: Audit; private config: IKuberoConfig; @@ -24,17 +29,17 @@ export class NotificationsService { } public setConfig(config: IKuberoConfig) { - this.config = config; + this.config = config; } - + public send(message: INotification) { - this.sendWebsocketMessage(message); - this.createKubernetesEvent(message); - this.writeAuditLog(message) + this.sendWebsocketMessage(message); + this.createKubernetesEvent(message); + this.writeAuditLog(message); - this.sendAllCustomNotification(this.config.notifications, message); + this.sendAllCustomNotification(this.config.notifications, message); - /* requires configuration in pipeline and app form + /* requires configuration in pipeline and app form if (message.data && message.data.app && message.data.app.notifications) { this.sendAllCustomNotification(message.data.app.notifications, message); } @@ -45,126 +50,176 @@ export class NotificationsService { */ } private sendWebsocketMessage(n: INotification) { - this.eventsGateway.sendEvent(n.name, n); + this.eventsGateway.sendEvent(n.name, n); } private createKubernetesEvent(n: INotification) { - this.kubectl.createEvent( - 'Normal', - n.action.replace(/^./, str => str.toUpperCase()), - n.name, - n.message, - ); + this.kubectl.createEvent( + 'Normal', + n.action.replace(/^./, (str) => str.toUpperCase()), + n.name, + n.message, + ); } private writeAuditLog(n: INotification) { - this.auditService.log({ - action: n.action, - user: n.user, - severity: n.severity, - namespace: n.appName+'-'+n.phaseName, - phase: n.phaseName, - app: n.appName, - pipeline: n.pipelineName, - resource: n.resource, - message: n.message, - }); + this.auditService.log({ + action: n.action, + user: n.user, + severity: n.severity, + namespace: n.appName + '-' + n.phaseName, + phase: n.phaseName, + app: n.appName, + pipeline: n.pipelineName, + resource: n.resource, + message: n.message, + }); } public sendDelayed(message: INotification) { - setTimeout(() => { - this.send(message); - }, 1000); + setTimeout(() => { + this.send(message); + }, 1000); } - private sendAllCustomNotification(notifications: INotificationConfig[], message: INotification) { - if (!notifications) { - return; + private sendAllCustomNotification( + notifications: INotificationConfig[], + message: INotification, + ) { + if (!notifications) { + return; + } + notifications.forEach((notification) => { + if ( + notification.enabled && + notification.events && + notification.events?.includes(message.name) && + (notification.pipelines?.length == 0 || + notification.pipelines?.includes('all') || + notification.pipelines?.includes(message.pipelineName)) + ) { + this.sendCustomNotification(notification.type, notification.config, { + name: notification.name, + user: message.user, + resource: message.resource, + action: message.action, + severity: message.severity, + message: message.message, + phaseName: message.phaseName, + pipelineName: message.pipelineName, + appName: message.appName, + data: message.data, + }); } - notifications.forEach(notification => { - if (notification.enabled && - notification.events && - notification.events?.includes(message.name) && - (notification.pipelines?.length == 0 || notification.pipelines?.includes('all') || notification.pipelines?.includes(message.pipelineName)) - ) { - this.sendCustomNotification(notification.type, - notification.config, - { - name: notification.name, - user: message.user, - resource: message.resource, - action: message.action, - severity: message.severity, - message: message.message, - phaseName: message.phaseName, - pipelineName: message.pipelineName, - appName: message.appName, - data: message.data - }); - } - }); + }); } - private sendCustomNotification(type: string, config: any, message: INotification) { - switch (type) { - case 'slack': - this.sendSlackNotification(message, config as INotificationSlack); - break; - case 'webhook': - this.sendWebhookNotification(message, config as INotificationWebhook); - break; - case 'discord': - this.sendDiscordNotification(message, config as INotificationDiscord); - break; - default: - console.log('unknown notification type', type); - break; - } + private sendCustomNotification( + type: string, + config: any, + message: INotification, + ) { + switch (type) { + case 'slack': + this.sendSlackNotification(message, config as INotificationSlack); + break; + case 'webhook': + this.sendWebhookNotification(message, config as INotificationWebhook); + break; + case 'discord': + this.sendDiscordNotification(message, config as INotificationDiscord); + break; + default: + console.log('unknown notification type', type); + break; + } } - private sendSlackNotification(message: INotification, config: INotificationSlack) { - // Docs: https://api.slack.com/messaging/webhooks#posting_with_webhooks - fetch(config.url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - text: message.message, - }) - }) - .then( res => console.log('Slack notification sent to '+config.url+' with status '+res.status)) + private sendSlackNotification( + message: INotification, + config: INotificationSlack, + ) { + // Docs: https://api.slack.com/messaging/webhooks#posting_with_webhooks + fetch(config.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + text: message.message, + }), + }) + .then((res) => + console.log( + 'Slack notification sent to ' + + config.url + + ' with status ' + + res.status, + ), + ) //.then(json => console.log(json)); - .catch( err => console.log('Slack notification failed to '+config.url+' with error '+err)) + .catch((err) => + console.log( + 'Slack notification failed to ' + config.url + ' with error ' + err, + ), + ); } - private sendWebhookNotification(message: INotification, config: INotificationWebhook) { - fetch(config.url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - message: message, - secret: config.secret - }) - }) - .then( res => console.log('Webhook notification sent to '+config.url+' with status '+res.status)) - .catch( err => console.log('Webhook notification failed to '+config.url+' with error '+err)) + private sendWebhookNotification( + message: INotification, + config: INotificationWebhook, + ) { + fetch(config.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: message, + secret: config.secret, + }), + }) + .then((res) => + console.log( + 'Webhook notification sent to ' + + config.url + + ' with status ' + + res.status, + ), + ) + .catch((err) => + console.log( + 'Webhook notification failed to ' + config.url + ' with error ' + err, + ), + ); } - private sendDiscordNotification(message: INotification, config: INotificationDiscord) { - //Docs: https://discord.com/developers/docs/resources/webhook#execute-webhook - fetch(config.url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - content: message.message, - }) - }) - .then( res => console.log('Discord notification sent to '+config.url+' with status '+res.status)) - .catch( err => console.log('Discord notification failed to '+config.url+' with error '+err)) + private sendDiscordNotification( + message: INotification, + config: INotificationDiscord, + ) { + //Docs: https://discord.com/developers/docs/resources/webhook#execute-webhook + fetch(config.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + content: message.message, + }), + }) + .then((res) => + console.log( + 'Discord notification sent to ' + + config.url + + ' with status ' + + res.status, + ), + ) + .catch((err) => + console.log( + 'Discord notification failed to ' + config.url + ' with error ' + err, + ), + ); } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts b/server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts index ffc649f4..915587ca 100644 --- a/server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts +++ b/server-refactored-v3/src/pipelines/dto/getPipeline.dto.ts @@ -1,7 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; export class GetPipelineDTO { - @ApiProperty({ type: () => App, isArray: true }) items: Array; } @@ -10,129 +9,129 @@ class App { @ApiProperty() buildpack: { build: { - command: string - readOnlyAppStorage?: boolean - repository: string + command: string; + readOnlyAppStorage?: boolean; + repository: string; securityContext: { - allowPrivilegeEscalation: boolean + allowPrivilegeEscalation: boolean; capabilities: { - add: Array - drop: Array - } - readOnlyRootFilesystem: boolean - runAsGroup: number - runAsNonRoot: boolean - runAsUser: number - } - tag: string - } + add: Array; + drop: Array; + }; + readOnlyRootFilesystem: boolean; + runAsGroup: number; + runAsNonRoot: boolean; + runAsUser: number; + }; + tag: string; + }; fetch: { - readOnlyAppStorage?: boolean - repository: string + readOnlyAppStorage?: boolean; + repository: string; securityContext: { - allowPrivilegeEscalation: boolean + allowPrivilegeEscalation: boolean; capabilities: { - add: Array - drop: Array - } - readOnlyRootFilesystem: boolean - runAsGroup: number - runAsNonRoot: boolean - runAsUser: number - } - tag: string - } - language: string - name: string + add: Array; + drop: Array; + }; + readOnlyRootFilesystem: boolean; + runAsGroup: number; + runAsNonRoot: boolean; + runAsUser: number; + }; + tag: string; + }; + language: string; + name: string; run: { - command: string - readOnlyAppStorage: boolean - repository: string + command: string; + readOnlyAppStorage: boolean; + repository: string; securityContext: { - allowPrivilegeEscalation: boolean + allowPrivilegeEscalation: boolean; capabilities: { - add: Array - drop: Array - } - readOnlyRootFilesystem: boolean - runAsGroup: number - runAsNonRoot: boolean - runAsUser: number - } - tag: string - } - } + add: Array; + drop: Array; + }; + readOnlyRootFilesystem: boolean; + runAsGroup: number; + runAsNonRoot: boolean; + runAsUser: number; + }; + tag: string; + }; + }; @ApiProperty() - buildstrategy: string - + buildstrategy: string; + @ApiProperty() - deploymentstrategy: string - + deploymentstrategy: string; + @ApiProperty() - dockerimage: string - + dockerimage: string; + @ApiProperty() - domain: string - + domain: string; + @ApiProperty() git: { keys: { - priv: string - pub: string - created_at?: string - id?: number - read_only?: boolean - title?: string - url?: string - verified?: boolean - } - provider: string + priv: string; + pub: string; + created_at?: string; + id?: number; + read_only?: boolean; + title?: string; + url?: string; + verified?: boolean; + }; + provider: string; repository: { - admin: boolean - clone_url: string - ssh_url: string - default_branch?: string - description?: string - homepage?: string - id?: number - language?: string - name?: string - node_id?: string - owner?: string - private?: boolean - push?: boolean - visibility?: string - } + admin: boolean; + clone_url: string; + ssh_url: string; + default_branch?: string; + description?: string; + homepage?: string; + id?: number; + language?: string; + name?: string; + node_id?: string; + owner?: string; + private?: boolean; + push?: boolean; + visibility?: string; + }; webhook: { - active?: boolean - created_at?: string - events?: Array - id?: number - insecure?: string - url?: string - } - } - + active?: boolean; + created_at?: string; + events?: Array; + id?: number; + insecure?: string; + url?: string; + }; + }; + @ApiProperty() - name: string - + name: string; + @ApiProperty() phases: Array<{ - context: string - defaultEnvvars: Array - domain: string - enabled: boolean - name: string - }> - + context: string; + defaultEnvvars: Array; + domain: string; + enabled: boolean; + name: string; + }>; + @ApiProperty() registry: { - host: string - password: string - username: string - } - + host: string; + password: string; + username: string; + }; + @ApiProperty() - reviewapps: boolean + reviewapps: boolean; } diff --git a/server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts b/server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts index d9293a12..c175d3b7 100644 --- a/server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts +++ b/server-refactored-v3/src/pipelines/dto/replacePipeline.dto.ts @@ -1,40 +1,38 @@ - import { ApiProperty } from '@nestjs/swagger'; import { IBuildpack, IRegistry } from '../../settings/settings.interface'; import { IPipelinePhase, IgitLink } from '../pipelines.interface'; export class CreatePipelineDTO { - @ApiProperty() pipelineName: string; - + @ApiProperty() domain: string; - + @ApiProperty() reviewapps: boolean; - + @ApiProperty() phases: IPipelinePhase[]; - + @ApiProperty() - buildpack: IBuildpack - + buildpack: IBuildpack; + @ApiProperty() git: IgitLink; - + @ApiProperty() registry: IRegistry; - + @ApiProperty() dockerimage: string; - + @ApiProperty() deploymentstrategy: 'git' | 'docker'; - + @ApiProperty() buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; - + @ApiProperty() resourceVersion?: string; // required to update resource, not part of spec -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts b/server-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts index 0b5583cd..edec9fcc 100644 --- a/server-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts +++ b/server-refactored-v3/src/pipelines/pipeline/pipeline.spec.ts @@ -8,7 +8,7 @@ describe('Pipeline', () => { dockerimage: 'test-image', reviewapps: true, phases: [], - buildpack: { + buildpack: { name: 'test-buildpack', language: 'nodejs', fetch: { @@ -23,9 +23,9 @@ describe('Pipeline', () => { allowPrivilegeEscalation: false, capabilities: { add: [], - drop: [] - } - } + drop: [], + }, + }, }, build: { repository: 'https://github.com/test/repo', @@ -39,9 +39,9 @@ describe('Pipeline', () => { allowPrivilegeEscalation: false, capabilities: { add: [], - drop: [] - } - } + drop: [], + }, + }, }, run: { repository: 'https://github.com/test/repo', @@ -55,11 +55,11 @@ describe('Pipeline', () => { allowPrivilegeEscalation: false, capabilities: { add: [], - drop: [] - } - } + drop: [], + }, + }, }, - tag: 'latest' + tag: 'latest', }, deploymentstrategy: 'git', buildstrategy: 'plain', @@ -68,14 +68,13 @@ describe('Pipeline', () => { repository: { admin: true, }, - webhook: {} - + webhook: {}, }, - registry: { + registry: { host: 'test-host', username: 'test-user', - password: 'test-password' - } + password: 'test-password', + }, }; it('should be defined', () => { diff --git a/server-refactored-v3/src/pipelines/pipeline/pipeline.ts b/server-refactored-v3/src/pipelines/pipeline/pipeline.ts index 5b84faed..8dadcaa7 100644 --- a/server-refactored-v3/src/pipelines/pipeline/pipeline.ts +++ b/server-refactored-v3/src/pipelines/pipeline/pipeline.ts @@ -1,58 +1,55 @@ -import { - IPipeline, +import { + IPipeline, IPipelinePhase, IgitLink, IKubectlPipeline, - IRegistry + IRegistry, } from '../pipelines.interface'; import { IBuildpack } from '../../settings/settings.interface'; import { IKubectlMetadata } from '../../kubernetes/kubernetes.interface'; - export class Pipeline implements IPipeline { - public name: string; - public domain: string; - public dockerimage: string; - public reviewapps: boolean; - public phases: IPipelinePhase[]; - public buildpack: IBuildpack; - public deploymentstrategy: 'git' | 'docker'; - public buildstrategy : 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; - public git: IgitLink; - public registry: IRegistry; + public name: string; + public domain: string; + public dockerimage: string; + public reviewapps: boolean; + public phases: IPipelinePhase[]; + public buildpack: IBuildpack; + public deploymentstrategy: 'git' | 'docker'; + public buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; + public git: IgitLink; + public registry: IRegistry; - constructor( - pl: IPipeline, - ) { - this.name = pl.name; - this.domain = pl.domain; - this.reviewapps = pl.reviewapps; - this.phases = pl.phases; - this.buildpack = pl.buildpack; - this.dockerimage = pl.dockerimage; - this.deploymentstrategy = pl.deploymentstrategy; - this.buildstrategy = pl.buildstrategy; - this.git = pl.git; - this.registry = pl.registry; - } + constructor(pl: IPipeline) { + this.name = pl.name; + this.domain = pl.domain; + this.reviewapps = pl.reviewapps; + this.phases = pl.phases; + this.buildpack = pl.buildpack; + this.dockerimage = pl.dockerimage; + this.deploymentstrategy = pl.deploymentstrategy; + this.buildstrategy = pl.buildstrategy; + this.git = pl.git; + this.registry = pl.registry; + } } export class KubectlPipeline implements IKubectlPipeline { - public apiVersion: string; - public kind: string; - public metadata: IKubectlMetadata; - public spec: Pipeline; + public apiVersion: string; + public kind: string; + public metadata: IKubectlMetadata; + public spec: Pipeline; - constructor(pipeline: IPipeline) { - this.apiVersion = "application.kubero.dev/v1alpha1"; - this.kind = "KuberoPipeline"; - this.metadata = { - name: pipeline.name, - labels: { - manager: 'kubero', - }, - }; - this.spec = pipeline; - } + constructor(pipeline: IPipeline) { + this.apiVersion = 'application.kubero.dev/v1alpha1'; + this.kind = 'KuberoPipeline'; + this.metadata = { + name: pipeline.name, + labels: { + manager: 'kubero', + }, + }; + this.spec = pipeline; + } } diff --git a/server-refactored-v3/src/pipelines/pipelines.controller.ts b/server-refactored-v3/src/pipelines/pipelines.controller.ts index 04f9380d..fbca41c0 100644 --- a/server-refactored-v3/src/pipelines/pipelines.controller.ts +++ b/server-refactored-v3/src/pipelines/pipelines.controller.ts @@ -1,4 +1,16 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Logger, Param, Post, Put } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpException, + HttpStatus, + Logger, + Param, + Post, + Put, +} from '@nestjs/common'; import { PipelinesService } from './pipelines.service'; import { ApiOkResponse, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { CreatePipelineDTO } from './dto/replacePipeline.dto'; @@ -9,13 +21,12 @@ import { IPipeline } from './pipelines.interface'; @Controller({ path: 'api/pipelines', version: '1' }) export class PipelinesController { - constructor(private pipelinesService: PipelinesService) {} @ApiOkResponse({ description: 'A List of Pipelines', type: GetPipelineDTO, - isArray: false + isArray: false, }) @ApiOperation({ summary: 'Get all pipelines' }) @Get('/') @@ -26,16 +37,15 @@ export class PipelinesController { @ApiOkResponse({ description: 'A List of Pipelines', type: OKDTO, - isArray: false + isArray: false, }) @ApiOperation({ summary: 'Create a new pipeline' }) @Post('/:pipeline') @HttpCode(HttpStatus.CREATED) async createPipeline( @Param('pipeline') pipelineName: string, - @Body() pl: CreatePipelineDTO + @Body() pl: CreatePipelineDTO, ): Promise { - if (pipelineName !== 'new') { const msg = 'Pipeline name does not match the URL'; Logger.error(msg); @@ -47,78 +57,78 @@ export class PipelinesController { id: 1, method: 'local', username: 'admin', - apitoken: '1234567890' + apitoken: '1234567890', }; - + const pipeline: IPipeline = { - name: pl.pipelineName, - domain: pl.domain, - phases: pl.phases, - buildpack: pl.buildpack, - reviewapps: pl.reviewapps, - dockerimage: pl.dockerimage, - git: pl.git, - registry: pl.registry as any, - deploymentstrategy: pl.deploymentstrategy, - buildstrategy: pl.buildstrategy, + name: pl.pipelineName, + domain: pl.domain, + phases: pl.phases, + buildpack: pl.buildpack, + reviewapps: pl.reviewapps, + dockerimage: pl.dockerimage, + git: pl.git, + registry: pl.registry as any, + deploymentstrategy: pl.deploymentstrategy, + buildstrategy: pl.buildstrategy, }; - return this.pipelinesService.createPipeline(pipeline, user) as Promise; + return this.pipelinesService.createPipeline( + pipeline, + user, + ) as Promise; } @ApiOperation({ summary: 'Get a specific pipeline' }) @Get('/:pipeline') - async getPipeline( - @Param('pipeline') pipeline: string, - ) { + async getPipeline(@Param('pipeline') pipeline: string) { return this.pipelinesService.getPipeline(pipeline); } @ApiOperation({ summary: 'Update a pipeline' }) @Put('/:pipeline') async updatePipeline(@Body() pl: CreatePipelineDTO) { - //TODO: Migration -> this is a mock user const user: IUser = { id: 1, method: 'local', username: 'admin', - apitoken: '1234567890' + apitoken: '1234567890', }; - + const pipeline: IPipeline = { - name: pl.pipelineName, - domain: pl.domain, - phases: pl.phases, - buildpack: pl.buildpack, - reviewapps: pl.reviewapps, - dockerimage: pl.dockerimage, - git: pl.git, - registry: pl.registry as any, - deploymentstrategy: pl.deploymentstrategy, - buildstrategy: pl.buildstrategy, + name: pl.pipelineName, + domain: pl.domain, + phases: pl.phases, + buildpack: pl.buildpack, + reviewapps: pl.reviewapps, + dockerimage: pl.dockerimage, + git: pl.git, + registry: pl.registry as any, + deploymentstrategy: pl.deploymentstrategy, + buildstrategy: pl.buildstrategy, }; - return this.pipelinesService.updatePipeline(pipeline, pl.resourceVersion as string, user); + return this.pipelinesService.updatePipeline( + pipeline, + pl.resourceVersion as string, + user, + ); } @ApiOperation({ summary: 'Delete a pipeline' }) @Delete('/:pipeline') - async deletePipeline( - @Param('pipeline') pipeline: string, - ) { + async deletePipeline(@Param('pipeline') pipeline: string) { const user: IUser = { id: 1, method: 'local', username: 'admin', - apitoken: '1234567890' + apitoken: '1234567890', }; return this.pipelinesService.deletePipeline(pipeline, user); } @ApiOperation({ summary: 'Get all apps for a pipeline' }) @Get('/:pipeline/apps') - async getPipelineApps( - @Param('pipeline') pipeline: string, - ) { + async getPipelineApps(@Param('pipeline') pipeline: string) { return this.pipelinesService.getPipelineWithApps(pipeline); } } diff --git a/server-refactored-v3/src/pipelines/pipelines.interface.ts b/server-refactored-v3/src/pipelines/pipelines.interface.ts index 6e0bb7b3..085fcc59 100644 --- a/server-refactored-v3/src/pipelines/pipelines.interface.ts +++ b/server-refactored-v3/src/pipelines/pipelines.interface.ts @@ -1,32 +1,32 @@ -import { IGithubRepository } from "src/apps/apps.interface"; -import { IBuildpack } from "src/settings/settings.interface"; -import { IKubectlMetadata } from "../kubernetes/kubernetes.interface"; +import { IGithubRepository } from 'src/apps/apps.interface'; +import { IBuildpack } from 'src/settings/settings.interface'; +import { IKubectlMetadata } from '../kubernetes/kubernetes.interface'; export interface IPipeline { name: string; domain: string; reviewapps: boolean; phases: IPipelinePhase[]; - buildpack: IBuildpack + buildpack: IBuildpack; git: IgitLink; registry: IRegistry; dockerimage: string; - deploymentstrategy: 'git' | 'docker', - buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks', + deploymentstrategy: 'git' | 'docker'; + buildstrategy: 'plain' | 'dockerfile' | 'nixpacks' | 'buildpacks'; resourceVersion?: string; // required to update resource, not part of spec } export interface IPipelineList { - items: IPipeline[], + items: IPipeline[]; } export interface IgitLink { keys: { - priv?: string, - pub?: string, - }, - provider?: string, - repository?: IGithubRepository + priv?: string; + pub?: string; + }; + provider?: string; + repository?: IGithubRepository; webhook: object; } @@ -48,12 +48,12 @@ export interface IRegistry { export interface IKubectlPipeline { apiVersion: string; kind: string; - metadata: IKubectlMetadata, - spec: IPipeline + metadata: IKubectlMetadata; + spec: IPipeline; } export interface IKubectlPipelineList { apiVersion: string; kind: string; - metadata: IKubectlMetadata, - items: IKubectlPipeline[] -} \ No newline at end of file + metadata: IKubectlMetadata; + items: IKubectlPipeline[]; +} diff --git a/server-refactored-v3/src/pipelines/pipelines.service.ts b/server-refactored-v3/src/pipelines/pipelines.service.ts index f9b3ffa3..96d33643 100644 --- a/server-refactored-v3/src/pipelines/pipelines.service.ts +++ b/server-refactored-v3/src/pipelines/pipelines.service.ts @@ -6,6 +6,7 @@ import { IUser } from '../auth/auth.interface'; import { NotificationsService } from '../notifications/notifications.service'; import { INotification } from '../notifications/notifications.interface'; import { CreatePipelineDTO } from './dto/replacePipeline.dto'; +import { IApp } from 'src/apps/apps.interface'; @Injectable() export class PipelinesService { @@ -15,13 +16,13 @@ export class PipelinesService { constructor( private kubectl: KubernetesService, private notificationsService: NotificationsService, -) {} + ) {} public async listPipelines(): Promise { - let pipelines = await this.kubectl.getPipelinesList(); + const pipelines = await this.kubectl.getPipelinesList(); const ret: IPipelineList = { - items: new Array() - } + items: [], + }; for (const pipeline of pipelines.items) { ret.items.push(pipeline.spec); } @@ -29,135 +30,162 @@ export class PipelinesService { } public async getPipelineWithApps(pipelineName: string) { - this.logger.debug('listApps in '+pipelineName); + this.logger.debug('listApps in ' + pipelineName); - await this.kubectl.setCurrentContext(process.env.KUBERO_CONTEXT || 'default'); + await this.kubectl.setCurrentContext( + process.env.KUBERO_CONTEXT || 'default', + ); const kpipeline = await this.kubectl.getPipeline(pipelineName); if (!kpipeline.spec || !kpipeline.spec.git || !kpipeline.spec.git.keys) { - return; + return; } - delete kpipeline.spec.git.keys.priv - delete kpipeline.spec.git.keys.pub + delete kpipeline.spec.git.keys.priv; + delete kpipeline.spec.git.keys.pub; - let pipeline = kpipeline.spec + const pipeline = kpipeline.spec; if (pipeline) { - for (const phase of pipeline.phases) { - if (phase.enabled == true) { - - const contextName = await this.getContext(pipelineName, phase.name); - if (contextName) { - const namespace = pipelineName+'-'+phase.name; - let apps = await this.kubectl.getAppsList(namespace, contextName); - - let appslist = new Array(); - for (const app of apps.items) { - appslist.push(app.spec); - } - // @ts-expect-error ts(2532) FIXME: Object is possibly 'undefined'. - pipeline.phases.find(p => p.name == phase.name).apps = appslist; - - } + for (const phase of pipeline.phases) { + if (phase.enabled == true) { + const contextName = await this.getContext(pipelineName, phase.name); + if (contextName) { + const namespace = pipelineName + '-' + phase.name; + const apps = await this.kubectl.getAppsList(namespace, contextName); + + const appslist = [] as IApp[]; + for (const app of apps.items) { + appslist.push(app.spec); } + // @ts-expect-error ts(2532) FIXME: Object is possibly 'undefined'. + pipeline.phases.find((p) => p.name == phase.name).apps = appslist; + } } + } } return pipeline; } - public async getContext(pipelineName: string, phaseName: string): Promise { - let context: string = 'missing-'+pipelineName+'-'+phaseName; - const pipelinesList = await this.listPipelines() + public async getContext( + pipelineName: string, + phaseName: string, + ): Promise { + let context: string = 'missing-' + pipelineName + '-' + phaseName; + const pipelinesList = await this.listPipelines(); for (const pipeline of pipelinesList.items) { - if (pipeline.name == pipelineName) { - for (const phase of pipeline.phases) { - if (phase.name == phaseName) { - //this.kubectl.setCurrentContext(phase.context); - context = phase.context; - } - } + if (pipeline.name == pipelineName) { + for (const phase of pipeline.phases) { + if (phase.name == phaseName) { + //this.kubectl.setCurrentContext(phase.context); + context = phase.context; + } } + } } - return context + return context; } - public async getPipeline(pipelineName: string): Promise{ - this.logger.debug('getPipeline: '+pipelineName); + public async getPipeline( + pipelineName: string, + ): Promise { + this.logger.debug('getPipeline: ' + pipelineName); - let pipeline = await this.kubectl.getPipeline(pipelineName) - .catch(error => { + const pipeline = await this.kubectl + .getPipeline(pipelineName) + .catch((error) => { this.logger.error(error); return undefined; - }); + }); if (pipeline) { - if (pipeline.spec.buildpack) { - pipeline.spec.buildpack.fetch.securityContext = Buildpack.SetSecurityContext(pipeline.spec.buildpack.fetch.securityContext); - pipeline.spec.buildpack.build.securityContext = Buildpack.SetSecurityContext(pipeline.spec.buildpack.build.securityContext); - pipeline.spec.buildpack.run.securityContext = Buildpack.SetSecurityContext(pipeline.spec.buildpack.run.securityContext); - } - - if (pipeline.metadata && pipeline.metadata.resourceVersion) { - pipeline.spec.resourceVersion = pipeline.metadata.resourceVersion; - } - - delete pipeline.spec.git.keys.priv - delete pipeline.spec.git.keys.pub - return pipeline.spec; + if (pipeline.spec.buildpack) { + pipeline.spec.buildpack.fetch.securityContext = + Buildpack.SetSecurityContext( + pipeline.spec.buildpack.fetch.securityContext, + ); + pipeline.spec.buildpack.build.securityContext = + Buildpack.SetSecurityContext( + pipeline.spec.buildpack.build.securityContext, + ); + pipeline.spec.buildpack.run.securityContext = + Buildpack.SetSecurityContext( + pipeline.spec.buildpack.run.securityContext, + ); + } + + if (pipeline.metadata && pipeline.metadata.resourceVersion) { + pipeline.spec.resourceVersion = pipeline.metadata.resourceVersion; + } + + delete pipeline.spec.git.keys.priv; + delete pipeline.spec.git.keys.pub; + return pipeline.spec; } } // delete a pipeline and all its namespaces/phases public deletePipeline(pipelineName: string, user: IUser) { - this.logger.debug('deletePipeline: '+pipelineName); + this.logger.debug('deletePipeline: ' + pipelineName); - if ( process.env.KUBERO_READONLY == 'true'){ - console.log('KUBERO_READONLY is set to true, not deleting pipeline '+ pipelineName); - return; + if (process.env.KUBERO_READONLY == 'true') { + console.log( + 'KUBERO_READONLY is set to true, not deleting pipeline ' + pipelineName, + ); + return; } - this.kubectl.getPipeline(pipelineName).then(async pipeline =>{ + this.kubectl + .getPipeline(pipelineName) + .then(async (pipeline) => { if (pipeline) { - await this.kubectl.deletePipeline(pipelineName); - - await new Promise(resolve => setTimeout(resolve, 1000)); // needs some extra time to delete the namespace - //this.updateState(); - - const m = { - 'name': 'updatePipeline', - 'user': user.username, - 'resource': 'pipeline', - 'action': 'delete', - 'severity': 'normal', - 'message': 'Deleted pipeline: '+pipelineName, - 'pipelineName':pipelineName, - 'phaseName': '', - 'appName': '', - 'data': { - 'pipeline': pipeline - } - } as INotification; - this.notificationsService.send(m); + await this.kubectl.deletePipeline(pipelineName); + + await new Promise((resolve) => setTimeout(resolve, 1000)); // needs some extra time to delete the namespace + //this.updateState(); + + const m = { + name: 'updatePipeline', + user: user.username, + resource: 'pipeline', + action: 'delete', + severity: 'normal', + message: 'Deleted pipeline: ' + pipelineName, + pipelineName: pipelineName, + phaseName: '', + appName: '', + data: { + pipeline: pipeline, + }, + } as INotification; + this.notificationsService.send(m); } - }) - .catch(error => { + }) + .catch((error) => { this.logger.error(error); - }); + }); } - public async updatePipeline(pipeline: IPipeline, resourceVersion: string, user: IUser) { - this.logger.debug('update Pipeline: '+pipeline.name); - - if ( process.env.KUBERO_READONLY == 'true'){ - this.logger.log('KUBERO_READONLY is set to true, not updating pipelline ' + pipeline.name); - return; + public async updatePipeline( + pipeline: IPipeline, + resourceVersion: string, + user: IUser, + ) { + this.logger.debug('update Pipeline: ' + pipeline.name); + + if (process.env.KUBERO_READONLY == 'true') { + this.logger.log( + 'KUBERO_READONLY is set to true, not updating pipelline ' + + pipeline.name, + ); + return; } - const currentPL = await this.kubectl.getPipeline(pipeline.name) - .catch(error => { + const currentPL = await this.kubectl + .getPipeline(pipeline.name) + .catch((error) => { this.logger.error(error); - }); + }); pipeline.git.keys.priv = currentPL?.spec.git.keys.priv; pipeline.git.keys.pub = currentPL?.spec.git.keys.pub; @@ -166,53 +194,54 @@ export class PipelinesService { await this.kubectl.updatePipeline(pipeline, resourceVersion); //this.updateState(); - await new Promise(resolve => setTimeout(resolve, 500)) + await new Promise((resolve) => setTimeout(resolve, 500)); const m = { - 'name': 'updatePipeline', - 'user': user.username, - 'resource': 'pipeline', - 'action': 'update', - 'severity': 'normal', - 'message': 'Updated pipeline: '+pipeline.name, - 'pipelineName':pipeline.name, - 'phaseName': '', - 'appName': '', - 'data': { - 'pipeline': pipeline - } + name: 'updatePipeline', + user: user.username, + resource: 'pipeline', + action: 'update', + severity: 'normal', + message: 'Updated pipeline: ' + pipeline.name, + pipelineName: pipeline.name, + phaseName: '', + appName: '', + data: { + pipeline: pipeline, + }, } as INotification; this.notificationsService.send(m); } public async createPipeline(pipeline: IPipeline, user: IUser) { - this.logger.debug('create Pipeline: '+pipeline.name); + this.logger.debug('create Pipeline: ' + pipeline.name); - if ( process.env.KUBERO_READONLY == 'true'){ - console.log('KUBERO_READONLY is set to true, not creting pipeline '+ pipeline.name); - return; + if (process.env.KUBERO_READONLY == 'true') { + console.log( + 'KUBERO_READONLY is set to true, not creting pipeline ' + pipeline.name, + ); + return; } // Create the Pipeline CRD await this.kubectl.createPipeline(pipeline); //this.updateState(); - + const m = { - 'name': 'updatePipeline', - 'user': user.username, - 'resource': 'pipeline', - 'action': 'created', - 'severity': 'normal', - 'message': 'Created new pipeline: '+pipeline.name, - 'pipelineName':pipeline.name, - 'phaseName': '', - 'appName': '', - 'data': { - 'pipeline': pipeline - } + name: 'updatePipeline', + user: user.username, + resource: 'pipeline', + action: 'created', + severity: 'normal', + message: 'Created new pipeline: ' + pipeline.name, + pipelineName: pipeline.name, + phaseName: '', + appName: '', + data: { + pipeline: pipeline, + }, } as INotification; this.notificationsService.send(m); - return { 'status': 'ok', 'message': 'Pipeline created: '+pipeline.name }; + return { status: 'ok', message: 'Pipeline created: ' + pipeline.name }; } - } diff --git a/server-refactored-v3/src/repo/git/bitbucket.ts b/server-refactored-v3/src/repo/git/bitbucket.ts index 33822e24..3aefc20b 100644 --- a/server-refactored-v3/src/repo/git/bitbucket.ts +++ b/server-refactored-v3/src/repo/git/bitbucket.ts @@ -1,376 +1,385 @@ import debug from 'debug'; -import * as crypto from "crypto" -import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import * as crypto from 'crypto'; +import { + IWebhook, + IRepository, + IWebhookR, + IDeploykeyR, + IPullrequest, +} from './types'; import { Repo } from './repo'; -import gitUrlParse = require("git-url-parse"); -debug('app:kubero:bitbucket:api') +import gitUrlParse = require('git-url-parse'); +debug('app:kubero:bitbucket:api'); -import { Bitbucket, APIClient } from "bitbucket" +import { Bitbucket, APIClient } from 'bitbucket'; import { RequestError } from '@octokit/types'; import { Logger } from '@nestjs/common'; export class BitbucketApi extends Repo { - private bitbucket: APIClient; - - constructor(username: string, appPassword: string) { - super("bitbucket"); - let clientOptions = { - notice: false, - auth: { - username: username, - password: appPassword - }, - } - - if (process.env.BITBUCKET_USERNAME && process.env.BITBUCKET_APP_PASSWORD) { - Logger.log('✅ BitBucket enabled', 'Feature'); - this.bitbucket = new Bitbucket(clientOptions) - } else { - this.bitbucket = new Bitbucket() - Logger.log("☑️ BitBucket disabled: No BITBUCKET_USERNAME or BITBUCKET_APP_PASSWORD set", 'Feature'); - } + private bitbucket: APIClient; + + constructor(username: string, appPassword: string) { + super('bitbucket'); + const clientOptions = { + notice: false, + auth: { + username: username, + password: appPassword, + }, + }; + + if (process.env.BITBUCKET_USERNAME && process.env.BITBUCKET_APP_PASSWORD) { + Logger.log('✅ BitBucket enabled', 'Feature'); + this.bitbucket = new Bitbucket(clientOptions); + } else { + this.bitbucket = new Bitbucket(); + Logger.log( + '☑️ BitBucket disabled: No BITBUCKET_USERNAME or BITBUCKET_APP_PASSWORD set', + 'Feature', + ); } - - protected async getRepository(gitrepo: string): Promise { - let ret: IRepository = { - status: 500, - statusText: 'error', - data: { - owner: 'unknown', - name: 'unknown', - admin: false, - push: false, - } - } - - let parsed = gitUrlParse(gitrepo) - let repo = parsed.name - let owner = parsed.owner - - //console.log(owner, repo); - try { - // https://bitbucketjs.netlify.app/#api-repositories-repositories_get - let res = await this.bitbucket.repositories.get({ - repo_slug: repo, - workspace: owner - }) - //console.log(res.data); - - ret = { - status: res.status, - statusText: 'found', - data: { - id: res.data.uuid, - node_id: res.data.full_name as string, - name: res.data.slug as string, - description: res.data.description, - owner: res.data.owner?.nickname as string, - private : res.data.is_private, - ssh_url: res.data.links?.clone?.find((c: any) => c.name === 'ssh')?.href as string, - clone_url: res.data.links?.clone?.find((c: any) => c.name === 'https')?.href as string, - language: res.data.language, - homepage: res.data.website as string, - admin: true, // assumed since we ar loading only owned repos - push: true, // assumed since we ar loading only owned repos - //visibility: res.data.visibility, - default_branch: res.data.mainbranch?.name as string, - } - } - - } catch (e) { - let res = e as RequestError; - this.logger.log("Repository not found: "+ gitrepo); - ret = { - status: res.status, - statusText: 'not found', - data: { - owner: owner, - name: repo, - admin: false, - push: false, - } - } - } - return ret; + } + + protected async getRepository(gitrepo: string): Promise { + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + }, + }; + + const parsed = gitUrlParse(gitrepo); + const repo = parsed.name; + const owner = parsed.owner; + + //console.log(owner, repo); + try { + // https://bitbucketjs.netlify.app/#api-repositories-repositories_get + const res = await this.bitbucket.repositories.get({ + repo_slug: repo, + workspace: owner, + }); + //console.log(res.data); + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.uuid, + node_id: res.data.full_name as string, + name: res.data.slug as string, + description: res.data.description, + owner: res.data.owner?.nickname as string, + private: res.data.is_private, + ssh_url: res.data.links?.clone?.find((c: any) => c.name === 'ssh') + ?.href as string, + clone_url: res.data.links?.clone?.find((c: any) => c.name === 'https') + ?.href as string, + language: res.data.language, + homepage: res.data.website as string, + admin: true, // assumed since we ar loading only owned repos + push: true, // assumed since we ar loading only owned repos + //visibility: res.data.visibility, + default_branch: res.data.mainbranch?.name as string, + }, + }; + } catch (e) { + const res = e as RequestError; + this.logger.log('Repository not found: ' + gitrepo); + ret = { + status: res.status, + statusText: 'not found', + data: { + owner: owner, + name: repo, + admin: false, + push: false, + }, + }; } - - public async getRepositories() { - let res = await this.bitbucket.request('GET /user/repos', {}) - return res.data; + return ret; + } + + public async getRepositories() { + const res = await this.bitbucket.request('GET /user/repos', {}); + return res.data; + } + + protected async addWebhook( + owner: string, + repo: string, + url: string, + secret: string, + ): Promise { + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + }, + }; + + const webhooksList = await this.bitbucket.repositories.listWebhooks({ + repo_slug: repo, + workspace: owner, + }); + + const webhook = webhooksList.data.values?.find((w: any) => w.url === url); + if (webhook == undefined) { + try { + const res = await this.bitbucket.repositories.createWebhook({ + repo_slug: repo, + workspace: owner, + _body: { + description: 'Kubero webhook', + url: url, + active: true, + //skip_cert_verification: false, + events: ['pullrequest:created', 'repo:push'], + }, + }); + ret = { + status: 201, + statusText: 'created', + data: { + id: res.data.uuid as string, + active: res.data.active as boolean, + created_at: res.data.created_at as string, + url: res.data.url as string, + insecure: !res.data.skip_cert_verification, + events: res.data.events as string[], + }, + }; + } catch (e) { + this.logger.error(e); + } + } else { + this.logger.debug('Webhook already exists'); + + ret = { + status: 422, + statusText: 'created', + data: { + id: webhook.uuid as string, + active: webhook.active as boolean, + created_at: webhook.created_at as string, + url: webhook.url as string, + insecure: !webhook.skip_cert_verification, + events: webhook.events as string[], + }, + }; } - protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { - - let ret: IWebhookR = { - status: 500, - statusText: 'error', - data: { - id: 0, - active: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - insecure: true, - events: [], - } - } - - - let webhooksList = await this.bitbucket.repositories.listWebhooks({ - repo_slug: repo, - workspace: owner - }) - - let webhook = webhooksList.data.values?.find((w: any) => w.url === url); - if (webhook == undefined) { - try { - let res = await this.bitbucket.repositories.createWebhook({ - repo_slug: repo, - workspace: owner, - _body: { - description: "Kubero webhook", - url: url, - active: true, - //skip_cert_verification: false, - events: ["pullrequest:created", "repo:push"] - } - }) - ret = { - status: 201, - statusText: 'created', - data: { - id: res.data.uuid as string, - active: res.data.active as boolean, - created_at: res.data.created_at as string, - url: res.data.url as string, - insecure: !res.data.skip_cert_verification as boolean, - events: res.data.events as string[], - } - } - } catch (e) { - this.logger.error(e) - } - } else { - this.logger.debug("Webhook already exists") - - ret = { - status: 422, - statusText: 'created', - data: { - id: webhook.uuid as string, - active: webhook.active as boolean, - created_at: webhook.created_at as string, - url: webhook.url as string, - insecure: !webhook.skip_cert_verification as boolean, - events: webhook.events as string[], - } - } - - } - - return ret; + return ret; + } + + protected async addDeployKey( + owner: string, + repo: string, + ): Promise { + const keyPair = this.createDeployKeyPair(); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: 'bot@kubero', + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + + try { + // https://bitbucketjs.netlify.app/#api-repositories-repositories_createDeployKey + const res = await this.bitbucket.repositories.createDeployKey({ + label: 'bot@kubero', + key: keyPair.pubKey, + repo_slug: repo, + workspace: owner, + }); + + //console.log(res); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id as number, + title: res.data.label as string, + verified: true, + created_at: res.data.created_on as string, + url: '', + read_only: false, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + } catch (e) { + const res = e as RequestError; + this.logger.log('Error adding deploy key: ' + res); } - protected async addDeployKey(owner: string, repo: string): Promise { - - const keyPair = this.createDeployKeyPair(); - - let ret: IDeploykeyR = { - status: 500, - statusText: 'error', - data: { - id: 0, - title: "bot@kubero", - verified: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - read_only: true, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - - try { - // https://bitbucketjs.netlify.app/#api-repositories-repositories_createDeployKey - let res = await this.bitbucket.repositories.createDeployKey({ - label: "bot@kubero", - key: keyPair.pubKey, - repo_slug: repo, - workspace: owner - }); - - //console.log(res); - - - ret = { - status: res.status, - statusText: 'created', - data: { - id: res.data.id as number, - title: res.data.label as string, - verified: true, - created_at: res.data.created_on as string, - url: '', - read_only: false, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - } catch (e) { - let res = e as RequestError; - this.logger.log("Error adding deploy key: "+ res); - } - - return ret + return ret; + } + + public getWebhook( + event: string, + delivery: string, + body: any, + ): IWebhook | boolean { + // use github and gitea naming for the event + let github_event = event; + if (event === 'repo:push') { + github_event = 'push'; + } else if (event === 'pullrequest:created') { + github_event = 'pull_request'; + } else { + this.logger.log('ERROR: untranslated Bitbucket event: ' + event); + return false; } - public getWebhook(event: string, delivery: string, body: any): IWebhook | boolean { - - // use github and gitea naming for the event - let github_event = event; - if (event === 'repo:push') { - github_event = 'push'; - } else if (event === 'pullrequest:created') { - github_event = 'pull_request'; - } else { - this.logger.log('ERROR: untranslated Bitbucket event: '+event); - return false; - } - - let branch: string = 'main'; - let ssh_url: string = ''; - let action; - if (body.ref != undefined) { - let ref = body.ref - let refs = ref.split('/') - branch = refs[refs.length - 1] - ssh_url = body.repository.ssh_url - } else if (body.pull_request != undefined) { - action = body.action, - branch = body.pull_request.head.ref - ssh_url = body.pull_request.head.repo.ssh_url - } else { - ssh_url = body.repository.ssh_url - } - - try { - let webhook: IWebhook = { - repoprovider: 'bitbucket', - action: action, - event: github_event, - delivery: delivery, - body: body, - branch: branch, - verified: true, // bitbucket does not support verification with signatures :( - repo: { - ssh_url: ssh_url, - } - } - - return webhook; - } catch (error) { - this.logger.log(error) - return false; - } + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.ref != undefined) { + const ref = body.ref; + const refs = ref.split('/'); + branch = refs[refs.length - 1]; + ssh_url = body.repository.ssh_url; + } else if (body.pull_request != undefined) { + (action = body.action), (branch = body.pull_request.head.ref); + ssh_url = body.pull_request.head.repo.ssh_url; + } else { + ssh_url = body.repository.ssh_url; } - public async listRepos(): Promise { - let ret: string[] = []; - try { - // https://bitbucketjs.netlify.app/#api-repositories-repositories_listGlobal - const repos = await this.bitbucket.repositories.listGlobal({ role: 'member' }) - - if (repos.data.values != undefined) { - for (let repo of repos.data.values) { - if (repo.links != undefined && repo.links.clone != undefined) { - ret.push(repo.links.clone[1].href as string); - } - } - } - - } catch (error) { - this.logger.log(error) - } - return ret; + try { + const webhook: IWebhook = { + repoprovider: 'bitbucket', + action: action, + event: github_event, + delivery: delivery, + body: body, + branch: branch, + verified: true, // bitbucket does not support verification with signatures :( + repo: { + ssh_url: ssh_url, + }, + }; + + return webhook; + } catch (error) { + this.logger.log(error); + return false; } - - public async getBranches(gitrepo: string): Promise { - //https://bitbucketjs.netlify.app/#api-repositories-repositories_listBranches - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const branches = await this.bitbucket.repositories.listBranches({ - repo_slug: repo, - workspace: owner, - sort: '-name' - }) - if (branches.data.values != undefined) { - return branches.data.values.map((branch: any) => branch.name); - } - } catch (error) { - this.logger.log(error) + } + + public async listRepos(): Promise { + const ret: string[] = []; + try { + // https://bitbucketjs.netlify.app/#api-repositories-repositories_listGlobal + const repos = await this.bitbucket.repositories.listGlobal({ + role: 'member', + }); + + if (repos.data.values != undefined) { + for (const repo of repos.data.values) { + if (repo.links != undefined && repo.links.clone != undefined) { + ret.push(repo.links.clone[1].href as string); + } } - - return []; - + } + } catch (error) { + this.logger.log(error); + } + return ret; + } + + public async getBranches(gitrepo: string): Promise { + //https://bitbucketjs.netlify.app/#api-repositories-repositories_listBranches + + const { repo, owner } = this.parseRepo(gitrepo); + + try { + const branches = await this.bitbucket.repositories.listBranches({ + repo_slug: repo, + workspace: owner, + sort: '-name', + }); + if (branches.data.values != undefined) { + return branches.data.values.map((branch: any) => branch.name); + } + } catch (error) { + this.logger.log(error); } + return []; + } + + public async getReferences(gitrepo: string): Promise { + let ret: string[] = []; + + const { repo, owner } = this.parseRepo(gitrepo); + + try { + const branches = await this.bitbucket.repositories.listBranches({ + repo_slug: repo, + workspace: owner, + sort: '-name', + }); + if (branches.data.values != undefined) { + ret = branches.data.values.map((branch: any) => branch.name); + } + } catch (error) { + this.logger.log(error); + } - - public async getReferences(gitrepo: string): Promise{ - - let ret: string[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const branches = await this.bitbucket.repositories.listBranches({ - repo_slug: repo, - workspace: owner, - sort: '-name' - }) - if (branches.data.values != undefined) { - ret = branches.data.values.map((branch: any) => branch.name); - } - } catch (error) { - this.logger.log(error) - } - - try { - const tags = await this.bitbucket.repositories.listTags({ - repo_slug: repo, - workspace: owner, - sort: '-name' - }) - if (tags.data.values != undefined) { - ret = ret.concat(tags.data.values.map((tag: any) => tag.name)); - } - } catch (error) { - this.logger.log(error) - } - - try { - const commits = await this.bitbucket.repositories.listCommits({ - repo_slug: repo, - workspace: owner, - sort: '-date' - }) - if (commits.data.values != undefined) { - ret = ret.concat(commits.data.values.map((commit: any) => commit.hash)); - } - } catch (error) { - this.logger.log(error) - } - - return ret; - + try { + const tags = await this.bitbucket.repositories.listTags({ + repo_slug: repo, + workspace: owner, + sort: '-name', + }); + if (tags.data.values != undefined) { + ret = ret.concat(tags.data.values.map((tag: any) => tag.name)); + } + } catch (error) { + this.logger.log(error); } - public async getPullrequests(gitrepo: string): Promise{ + try { + const commits = await this.bitbucket.repositories.listCommits({ + repo_slug: repo, + workspace: owner, + sort: '-date', + }); + if (commits.data.values != undefined) { + ret = ret.concat(commits.data.values.map((commit: any) => commit.hash)); + } + } catch (error) { + this.logger.log(error); + } - let ret: IPullrequest[] = []; + return ret; + } + public async getPullrequests(gitrepo: string): Promise { + const ret: IPullrequest[] = []; - return ret; - } -} \ No newline at end of file + return ret; + } +} diff --git a/server-refactored-v3/src/repo/git/gitea.ts b/server-refactored-v3/src/repo/git/gitea.ts index be450e37..00258d8c 100644 --- a/server-refactored-v3/src/repo/git/gitea.ts +++ b/server-refactored-v3/src/repo/git/gitea.ts @@ -1,352 +1,368 @@ import debug from 'debug'; -import * as crypto from "crypto" -import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import * as crypto from 'crypto'; +import { + IWebhook, + IRepository, + IWebhookR, + IDeploykeyR, + IPullrequest, +} from './types'; import { Repo } from './repo'; -import gitUrlParse = require("git-url-parse"); -debug('app:kubero:gitea:api') +import gitUrlParse = require('git-url-parse'); +debug('app:kubero:gitea:api'); //https://www.npmjs.com/package/gitea-js -import { giteaApi } from "gitea-js" +import { giteaApi } from 'gitea-js'; import { fetch as fetchGitea } from 'cross-fetch'; export class GiteaApi extends Repo { - private gitea: any; - - constructor(baseURL: string, token: string) { - super("gitea"); - this.gitea = giteaApi(baseURL, { - token: token, - customFetch: fetchGitea, - }); - } - - protected async getRepository(gitrepo: string): Promise { - let ret: IRepository = { - status: 500, - statusText: 'error', - data: { - owner: 'unknown', - name: 'unknown', - admin: false, - push: false, - } - } - - let parsed = gitUrlParse(gitrepo) - let repo = parsed.name - let owner = parsed.owner - - let res = await this.gitea.repos.repoGet(owner, repo) - .catch((error: any) => { - this.logger.error(error) - return ret; - }) - + private gitea: any; + + constructor(baseURL: string, token: string) { + super('gitea'); + this.gitea = giteaApi(baseURL, { + token: token, + customFetch: fetchGitea, + }); + } + + protected async getRepository(gitrepo: string): Promise { + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + }, + }; + + const parsed = gitUrlParse(gitrepo); + const repo = parsed.name; + const owner = parsed.owner; + + const res = await this.gitea.repos + .repoGet(owner, repo) + .catch((error: any) => { + this.logger.error(error); + return ret; + }); + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.id, + node_id: res.data.node_id, + name: res.data.name, + description: res.data.description, + owner: res.data.owner.login, + private: res.data.private, + ssh_url: res.data.ssh_url, + language: res.data.language, + homepage: res.data.homepage, + admin: res.data.permissions.admin, + push: res.data.permissions.push, + visibility: res.data.visibility, + default_branch: res.data.default_branch, + }, + }; + return ret; + } + + protected async addWebhook( + owner: string, + repo: string, + url: string, + secret: string, + ): Promise { + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + }, + }; + + //https://try.gitea.io/api/swagger#/repository/repoListHooks + const webhooksList = await this.gitea.repos + .repoListHooks(owner, repo) + .catch((error: any) => { + this.logger.error(error); + return ret; + }); + + // try to find the webhook + for (const webhook of webhooksList.data) { + if ( + webhook.config.url === url && + webhook.config.content_type === 'json' && + webhook.active === true + ) { ret = { - status: res.status, - statusText: 'found', - data: { - id: res.data.id, - node_id: res.data.node_id, - name: res.data.name, - description: res.data.description, - owner: res.data.owner.login, - private : res.data.private, - ssh_url: res.data.ssh_url, - language: res.data.language, - homepage: res.data.homepage, - admin: res.data.permissions.admin, - push: res.data.permissions.push, - visibility: res.data.visibility, - default_branch: res.data.default_branch, - } - } + status: 422, + statusText: 'found', + data: webhook, + }; return ret; - + } } - - protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { - - let ret: IWebhookR = { - status: 500, - statusText: 'error', - data: { - id: 0, - active: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - insecure: true, - events: [], - } - } - - //https://try.gitea.io/api/swagger#/repository/repoListHooks - const webhooksList = await this.gitea.repos.repoListHooks(owner, repo) - .catch((error: any) => { - this.logger.error(error) - return ret; - }) - - // try to find the webhook - for (let webhook of webhooksList.data) { - if (webhook.config.url === url && - webhook.config.content_type === 'json' && - webhook.active === true) { - ret = { - status: 422, - statusText: 'found', - data: webhook, - } - return ret; - } - } - //console.log(webhooksList) - - // create the webhook since it does not exist - try { - - //https://try.gitea.io/api/swagger#/repository/repoCreateHook - let res = await this.gitea.repos.repoCreateHook(owner, repo, { - active: true, - config: { - url: url, - content_type: "json", - secret: secret, - insecure_ssl: '0' - }, - events: [ - "push", - "pull_request" - ], - type: "gitea" - }); - - ret = { - status: res.status, - statusText: 'created', - data: { - id: res.data.id, - active: res.data.active, - created_at: res.data.created_at, - url: res.data.url, - insecure: res.data.config.insecure_ssl, - events: res.data.events, - } - } - } catch (e) { - this.logger.error(e) - } - return ret; + //console.log(webhooksList) + + // create the webhook since it does not exist + try { + //https://try.gitea.io/api/swagger#/repository/repoCreateHook + const res = await this.gitea.repos.repoCreateHook(owner, repo, { + active: true, + config: { + url: url, + content_type: 'json', + secret: secret, + insecure_ssl: '0', + }, + events: ['push', 'pull_request'], + type: 'gitea', + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + active: res.data.active, + created_at: res.data.created_at, + url: res.data.url, + insecure: res.data.config.insecure_ssl, + events: res.data.events, + }, + }; + } catch (e) { + this.logger.error(e); + } + return ret; + } + + protected async addDeployKey( + owner: string, + repo: string, + ): Promise { + const keyPair = this.createDeployKeyPair(); + + const title: string = 'bot@kubero.' + crypto.randomBytes(4).toString('hex'); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: title, + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + + try { + //https://try.gitea.io/api/swagger#/repository/repoCreateKey + const res = await this.gitea.repos.repoCreateKey(owner, repo, { + title: title, + key: keyPair.pubKey, + read_only: true, + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + title: res.data.title, + verified: res.data.verified, + created_at: res.data.created_at, + url: res.data.url, + read_only: res.data.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + } catch (e) { + this.logger.error(e); } - - protected async addDeployKey(owner: string, repo: string): Promise { - - const keyPair = this.createDeployKeyPair(); - - const title: string = "bot@kubero."+crypto.randomBytes(4).toString('hex'); - - let ret: IDeploykeyR = { - status: 500, - statusText: 'error', - data: { - id: 0, - title: title, - verified: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - read_only: true, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - - try { - //https://try.gitea.io/api/swagger#/repository/repoCreateKey - let res = await this.gitea.repos.repoCreateKey(owner, repo, { - title: title, - key: keyPair.pubKey, - read_only: true - }); - - ret = { - status: res.status, - statusText: 'created', - data: { - id: res.data.id, - title: res.data.title, - verified: res.data.verified, - created_at: res.data.created_at, - url: res.data.url, - read_only: res.data.read_only, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - } catch (e) { - this.logger.error(e) - } - - return ret + return ret; + } + + public getWebhook( + event: string, + delivery: string, + signature: string, + body: any, + ): IWebhook | boolean { + //https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks + const secret = process.env.KUBERO_WEBHOOK_SECRET as string; + const hash = + 'sha256=' + + crypto + .createHmac('sha256', secret) + .update(JSON.stringify(body, null, ' ')) + .digest('hex'); + + let verified = false; + if (hash === signature) { + debug.debug('Gitea webhook signature is valid for event: ' + delivery); + verified = true; + } else { + this.logger.log('ERROR: invalid signature for event: ' + delivery); + this.logger.log('Hash: ' + hash); + this.logger.log('Signature: ' + signature); + verified = false; + return false; } - public getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean { - //https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks - let secret = process.env.KUBERO_WEBHOOK_SECRET as string; - let hash = 'sha256='+crypto.createHmac('sha256', secret).update(JSON.stringify(body, null, ' ')).digest('hex') - - let verified = false; - if (hash === signature) { - debug.debug('Gitea webhook signature is valid for event: '+delivery); - verified = true; - } else { - this.logger.log('ERROR: invalid signature for event: '+delivery); - this.logger.log('Hash: '+hash); - this.logger.log('Signature: '+signature); - verified = false; - return false; - } - - let branch: string = 'main'; - let ssh_url: string = ''; - let action; - if (body.pull_request == undefined) { - let ref = body.ref - let refs = ref.split('/') - branch = refs[refs.length - 1] - ssh_url = body.repository.ssh_url - } else if (body.pull_request != undefined) { - action = body.action, - branch = body.pull_request.head.ref - ssh_url = body.pull_request.head.repo.ssh_url - } else { - ssh_url = body.repository.ssh_url - } - - try { - let webhook: IWebhook = { - repoprovider: 'gitea', - action: action, - event: event, - delivery: delivery, - body: body, - branch: branch, - verified: verified, - repo: { - ssh_url: ssh_url, - } - } - - return webhook; - } catch (error) { - this.logger.error(error) - return false; - } + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.pull_request == undefined) { + const ref = body.ref; + const refs = ref.split('/'); + branch = refs[refs.length - 1]; + ssh_url = body.repository.ssh_url; + } else if (body.pull_request != undefined) { + (action = body.action), (branch = body.pull_request.head.ref); + ssh_url = body.pull_request.head.repo.ssh_url; + } else { + ssh_url = body.repository.ssh_url; } - public async listRepos(): Promise { - let ret: string[] = []; - try { - const repos = await this.gitea.user.userCurrentListRepos() - for (let repo of repos.data) { - ret.push(repo.ssh_url) - } - } catch (error) { - this.logger.error(error) - } - return ret; + try { + const webhook: IWebhook = { + repoprovider: 'gitea', + action: action, + event: event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + }, + }; + + return webhook; + } catch (error) { + this.logger.error(error); + return false; + } + } + + public async listRepos(): Promise { + const ret: string[] = []; + try { + const repos = await this.gitea.user.userCurrentListRepos(); + for (const repo of repos.data) { + ret.push(repo.ssh_url); + } + } catch (error) { + this.logger.error(error); + } + return ret; + } + + public async getBranches(gitrepo: string): Promise { + // https://try.gitea.io/api/swagger#/repository/repoListBranches + const ret: string[] = []; + + const { repo, owner } = this.parseRepo(gitrepo); + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo); + for (const branch of branches.data) { + ret.push(branch.name); + } + } catch (error) { + this.logger.error(error); } - public async getBranches(gitrepo: string): Promise{ - // https://try.gitea.io/api/swagger#/repository/repoListBranches - let ret: string[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - try { - const branches = await this.gitea.repos.repoListBranches(owner, repo) - for (let branch of branches.data) { - ret.push(branch.name) - } - } catch (error) { - this.logger.error(error) - } + return ret; + } - return ret; - } + public async getReferences(gitrepo: string): Promise { + const ret: string[] = []; - public async getReferences(gitrepo: string): Promise{ - - let ret: string[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const branches = await this.gitea.repos.repoListBranches(owner, repo) - for (let branch of branches.data) { - ret.push(branch.name) - } - } catch (error) { - this.logger.log(error) - } - - try { - const tags = await this.gitea.repos.repoListTags(owner, repo) - for (let tag of tags.data) { - ret.push(tag.name) - } - } catch (error) { - this.logger.log(error) - } - - try { - const commits = await this.gitea.repos.repoListCommits(owner, repo) - for (let commit of commits.data) { - ret.push(commit.sha) - } - } catch (error) { - this.logger.log(error) - } + const { repo, owner } = this.parseRepo(gitrepo); - return ret; + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo); + for (const branch of branches.data) { + ret.push(branch.name); + } + } catch (error) { + this.logger.log(error); + } + try { + const tags = await this.gitea.repos.repoListTags(owner, repo); + for (const tag of tags.data) { + ret.push(tag.name); + } + } catch (error) { + this.logger.log(error); } - public async getPullrequests(gitrepo: string): Promise{ - - let ret: IPullrequest[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const pulls = await this.gitea.repos.repoListPullRequests(owner, repo, { - state: "open", - sort: "recentupdate"}) - for (let pr of pulls.data) { - const p: IPullrequest = { - html_url: pr.url, - number: pr.number, - title: pr.title, - state: pr.state, - //draft: pr.draft, - user: { - login: pr.user.login, - avatar_url: pr.user.avatar_url, - }, - created_at: pr.created_at, - updated_at: pr.updated_at, - closed_at: pr.closed_at, - merged_at: pr.merged_at, - //locked: pr.locked, - branch: pr.head.ref, - ssh_url: pr.head.repo.ssh_url, - } - ret.push(p) - } - - } catch (error) { - this.logger.log(error) - } + try { + const commits = await this.gitea.repos.repoListCommits(owner, repo); + for (const commit of commits.data) { + ret.push(commit.sha); + } + } catch (error) { + this.logger.log(error); + } - return ret; + return ret; + } + + public async getPullrequests(gitrepo: string): Promise { + const ret: IPullrequest[] = []; + + const { repo, owner } = this.parseRepo(gitrepo); + + try { + const pulls = await this.gitea.repos.repoListPullRequests(owner, repo, { + state: 'open', + sort: 'recentupdate', + }); + for (const pr of pulls.data) { + const p: IPullrequest = { + html_url: pr.url, + number: pr.number, + title: pr.title, + state: pr.state, + //draft: pr.draft, + user: { + login: pr.user.login, + avatar_url: pr.user.avatar_url, + }, + created_at: pr.created_at, + updated_at: pr.updated_at, + closed_at: pr.closed_at, + merged_at: pr.merged_at, + //locked: pr.locked, + branch: pr.head.ref, + ssh_url: pr.head.repo.ssh_url, + }; + ret.push(p); + } + } catch (error) { + this.logger.log(error); } + + return ret; + } } diff --git a/server-refactored-v3/src/repo/git/github.ts b/server-refactored-v3/src/repo/git/github.ts index f8cedc5f..7ad94338 100644 --- a/server-refactored-v3/src/repo/git/github.ts +++ b/server-refactored-v3/src/repo/git/github.ts @@ -1,90 +1,96 @@ import debug from 'debug'; -import * as crypto from "crypto" -import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import * as crypto from 'crypto'; +import { + IWebhook, + IRepository, + IWebhookR, + IDeploykeyR, + IPullrequest, +} from './types'; import { Repo } from './repo'; -import gitUrlParse = require("git-url-parse"); -debug('app:kubero:github:api') +import gitUrlParse = require('git-url-parse'); +debug('app:kubero:github:api'); //const { Octokit } = require("@octokit/core"); -import { Octokit } from "@octokit/core" +import { Octokit } from '@octokit/core'; import { RequestError } from '@octokit/types'; export class GithubApi extends Repo { - private octokit: any; - - constructor(token: string) { - super("github"); - this.octokit = new Octokit({ - auth: token - }); + private octokit: any; + + constructor(token: string) { + super('github'); + this.octokit = new Octokit({ + auth: token, + }); + } + + protected async getRepository(gitrepo: string): Promise { + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + }, + }; + + const parsed = gitUrlParse(gitrepo); + const repo = parsed.name; + const owner = parsed.owner; + + try { + const res = await this.octokit.request('GET /repos/{owner}/{repo}', { + owner: owner, + repo: repo, + }); + //console.log(res.data); + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.id, + node_id: res.data.node_id, + name: res.data.name, + description: res.data.description, + owner: res.data.owner.login, + private: res.data.private, + ssh_url: res.data.ssh_url, + clone_url: res.data.clone_url, + language: res.data.language, + homepage: res.data.homepage, + admin: res.data.permissions.admin, + push: res.data.permissions.push, + visibility: res.data.visibility, + default_branch: res.data.default_branch, + }, + }; + } catch (e) { + const res = e as RequestError; + this.logger.log('Repository not found: ' + gitrepo); + ret = { + status: res.status, + statusText: 'not found', + data: { + owner: owner, + name: repo, + admin: false, + push: false, + }, + }; } + return ret; + } - protected async getRepository(gitrepo: string): Promise { - let ret: IRepository = { - status: 500, - statusText: 'error', - data: { - owner: 'unknown', - name: 'unknown', - admin: false, - push: false, - } - } - - let parsed = gitUrlParse(gitrepo) - let repo = parsed.name - let owner = parsed.owner + public async getRepositories() { + const res = await this.octokit.request('GET /user/repos', {}); + return res.data; + } - try { - let res = await this.octokit.request('GET /repos/{owner}/{repo}', { - owner: owner, - repo: repo, - }); - //console.log(res.data); - - ret = { - status: res.status, - statusText: 'found', - data: { - id: res.data.id, - node_id: res.data.node_id, - name: res.data.name, - description: res.data.description, - owner: res.data.owner.login, - private : res.data.private, - ssh_url: res.data.ssh_url, - clone_url: res.data.clone_url, - language: res.data.language, - homepage: res.data.homepage, - admin: res.data.permissions.admin, - push: res.data.permissions.push, - visibility: res.data.visibility, - default_branch: res.data.default_branch, - } - } - } catch (e) { - let res = e as RequestError; - this.logger.log("Repository not found: "+ gitrepo); - ret = { - status: res.status, - statusText: 'not found', - data: { - owner: owner, - name: repo, - admin: false, - push: false, - } - } - } - return ret; - } - - public async getRepositories() { - let res = await this.octokit.request('GET /user/repos', {}) - return res.data; - } - -/* + /* public async getRepositoryCommits(owner: string, repo: string, branch: string) { return await this.octokit.git.listCommits({ @@ -94,308 +100,339 @@ export class GithubApi extends Repo { }); } */ - protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { - - let ret: IWebhookR = { - status: 500, - statusText: 'error', - data: { - id: 0, - active: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - insecure: true, - events: [], - } - } - - try { - let res = await this.octokit.request('POST /repos/{owner}/{repo}/hooks', { - owner: owner, - repo: repo, - active: true, - config: { - url: url, - content_type: "json", - secret: secret, - insecure_ssl: '0' - }, - events: [ - "push", - "pull_request" - ] - }); + protected async addWebhook( + owner: string, + repo: string, + url: string, + secret: string, + ): Promise { + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + }, + }; + + try { + const res = await this.octokit.request( + 'POST /repos/{owner}/{repo}/hooks', + { + owner: owner, + repo: repo, + active: true, + config: { + url: url, + content_type: 'json', + secret: secret, + insecure_ssl: '0', + }, + events: ['push', 'pull_request'], + }, + ); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + active: res.data.active, + created_at: res.data.created_at, + url: res.data.url, + insecure: res.data.config.insecure_ssl, + events: res.data.events, + }, + }; + } catch (e) { + const res = e as RequestError; + if (res.status === 422) { + const existingWebhooksRes = await this.octokit.request( + 'GET /repos/{owner}/{repo}/hooks', + { + owner: owner, + repo: repo, + }, + ); + for (const webhook of existingWebhooksRes.data) { + if (webhook.config.url === url) { + this.logger.debug('Webhook already exists'); ret = { - status: res.status, - statusText: 'created', - data: { - id: res.data.id, - active: res.data.active, - created_at: res.data.created_at, - url: res.data.url, - insecure: res.data.config.insecure_ssl, - events: res.data.events, - } - } - } catch (e) { - let res = e as RequestError; - if (res.status === 422) { - let existingWebhooksRes = await this.octokit.request('GET /repos/{owner}/{repo}/hooks', { - owner: owner, - repo: repo, - }) - for (let webhook of existingWebhooksRes.data) { - if (webhook.config.url === url) { - this.logger.debug("Webhook already exists"); - - ret = { - status: res.status, - statusText: 'created', - data: { - id: webhook.id, - active: webhook.active, - created_at: webhook.created_at, - url: webhook.config.url, - insecure: webhook.config.insecure_ssl, - events: webhook.events, - } - } - } - } - } + status: res.status, + statusText: 'created', + data: { + id: webhook.id, + active: webhook.active, + created_at: webhook.created_at, + url: webhook.config.url, + insecure: webhook.config.insecure_ssl, + events: webhook.events, + }, + }; + } } - - return ret; + } } - protected async addDeployKey(owner: string, repo: string): Promise { - - const keyPair = this.createDeployKeyPair(); - - let ret: IDeploykeyR = { - status: 500, - statusText: 'error', - data: { - id: 0, - title: "bot@kubero", - verified: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - read_only: true, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - - try { - let res = await this.octokit.request('POST /repos/{owner}/{repo}/keys', { - owner: owner, - repo: repo, - title: "bot@kubero", - key: keyPair.pubKey, - read_only: true - }); - - ret = { - status: res.status, - statusText: 'created', - data: { - id: res.data.id, - title: res.data.title, - verified: res.data.verified, - created_at: res.data.created_at, - url: res.data.url, - read_only: res.data.read_only, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - } catch (e) { - let res = e as RequestError; - this.logger.log("Error adding deploy key: "+ res); - } - - return ret + return ret; + } + + protected async addDeployKey( + owner: string, + repo: string, + ): Promise { + const keyPair = this.createDeployKeyPair(); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: 'bot@kubero', + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + + try { + const res = await this.octokit.request( + 'POST /repos/{owner}/{repo}/keys', + { + owner: owner, + repo: repo, + title: 'bot@kubero', + key: keyPair.pubKey, + read_only: true, + }, + ); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + title: res.data.title, + verified: res.data.verified, + created_at: res.data.created_at, + url: res.data.url, + read_only: res.data.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + } catch (e) { + const res = e as RequestError; + this.logger.log('Error adding deploy key: ' + res); } - public getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean { - //https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks - let secret = process.env.KUBERO_WEBHOOK_SECRET as string; - let hash = 'sha256='+crypto.createHmac('sha256', secret).update(JSON.stringify(body)).digest('hex') - - let verified = false; - if (hash === signature) { - debug.debug('Github webhook signature is valid for event: '+delivery); - verified = true; - } else { - this.logger.log('ERROR: invalid signature for event: '+delivery); - this.logger.log('Hash: '+hash); - this.logger.log('Signature: '+signature); - verified = false; - return false; - } - - let branch: string = 'main'; - let ssh_url: string = ''; - let action; - if (body.ref != undefined) { - let ref = body.ref - let refs = ref.split('/') - branch = refs[refs.length - 1] - ssh_url = body.repository.ssh_url - } else if (body.pull_request != undefined) { - action = body.action, - branch = body.pull_request.head.ref - ssh_url = body.pull_request.head.repo.ssh_url - } else { - ssh_url = body.repository.ssh_url - } - - try { - let webhook: IWebhook = { - repoprovider: 'github', - action: action, - event: event, - delivery: delivery, - body: body, - branch: branch, - verified: verified, - repo: { - ssh_url: ssh_url, - } - } - - return webhook; - } catch (error) { - this.logger.log(error) - return false; - } + return ret; + } + + public getWebhook( + event: string, + delivery: string, + signature: string, + body: any, + ): IWebhook | boolean { + //https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks + const secret = process.env.KUBERO_WEBHOOK_SECRET as string; + const hash = + 'sha256=' + + crypto + .createHmac('sha256', secret) + .update(JSON.stringify(body)) + .digest('hex'); + + let verified = false; + if (hash === signature) { + debug.debug('Github webhook signature is valid for event: ' + delivery); + verified = true; + } else { + this.logger.log('ERROR: invalid signature for event: ' + delivery); + this.logger.log('Hash: ' + hash); + this.logger.log('Signature: ' + signature); + verified = false; + return false; } - public async listRepos(): Promise { - let ret: string[] = []; - try { - const repos = await this.octokit.request('GET /user/repos', { - visibility: 'all', - per_page: 100, - sort: 'updated' - }) - for (let repo of repos.data) { - ret.push(repo.ssh_url) - } - } catch (error) { - this.logger.log(error) - } - return ret; + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.ref != undefined) { + const ref = body.ref; + const refs = ref.split('/'); + branch = refs[refs.length - 1]; + ssh_url = body.repository.ssh_url; + } else if (body.pull_request != undefined) { + (action = body.action), (branch = body.pull_request.head.ref); + ssh_url = body.pull_request.head.repo.ssh_url; + } else { + ssh_url = body.repository.ssh_url; } - public async getBranches(gitrepo: string): Promise{ - - let ret: string[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const branches = await this.octokit.request('GET /repos/{owner}/{repo}/branches', { - owner: owner, - repo: repo, - }) - for (let branch of branches.data) { - ret.push(branch.name) - } - } catch (error) { - this.logger.log(error) - } - - return ret; - + try { + const webhook: IWebhook = { + repoprovider: 'github', + action: action, + event: event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + }, + }; + + return webhook; + } catch (error) { + this.logger.log(error); + return false; + } + } + + public async listRepos(): Promise { + const ret: string[] = []; + try { + const repos = await this.octokit.request('GET /user/repos', { + visibility: 'all', + per_page: 100, + sort: 'updated', + }); + for (const repo of repos.data) { + ret.push(repo.ssh_url); + } + } catch (error) { + this.logger.log(error); + } + return ret; + } + + public async getBranches(gitrepo: string): Promise { + const ret: string[] = []; + + const { repo, owner } = this.parseRepo(gitrepo); + + try { + const branches = await this.octokit.request( + 'GET /repos/{owner}/{repo}/branches', + { + owner: owner, + repo: repo, + }, + ); + for (const branch of branches.data) { + ret.push(branch.name); + } + } catch (error) { + this.logger.log(error); } - public async getReferences(gitrepo: string): Promise{ - - let ret: string[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const branches = await this.octokit.request('GET /repos/{owner}/{repo}/branches', { - owner: owner, - repo: repo, - }) - for (let branch of branches.data) { - ret.push(branch.name) - } - } catch (error) { - this.logger.log(error) - } - - try { - const tags = await this.octokit.request('GET /repos/{owner}/{repo}/tags', { - owner: owner, - repo: repo, - }) - for (let tag of tags.data) { - ret.push(tag.name) - } - } catch (error) { - this.logger.log(error) - } - - try { - const commits = await this.octokit.request('GET /repos/{owner}/{repo}/commits', { - owner: owner, - repo: repo, - }) - for (let commit of commits.data) { - ret.push(commit.sha) - } - } catch (error) { - this.logger.log(error) - } - - return ret; + return ret; + } + + public async getReferences(gitrepo: string): Promise { + const ret: string[] = []; + + const { repo, owner } = this.parseRepo(gitrepo); + + try { + const branches = await this.octokit.request( + 'GET /repos/{owner}/{repo}/branches', + { + owner: owner, + repo: repo, + }, + ); + for (const branch of branches.data) { + ret.push(branch.name); + } + } catch (error) { + this.logger.log(error); + } + try { + const tags = await this.octokit.request( + 'GET /repos/{owner}/{repo}/tags', + { + owner: owner, + repo: repo, + }, + ); + for (const tag of tags.data) { + ret.push(tag.name); + } + } catch (error) { + this.logger.log(error); } - public async getPullrequests(gitrepo: string): Promise{ - - let ret: IPullrequest[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const pulls = await this.octokit.request('GET /repos/{owner}/{repo}/pulls', { - owner: owner, - repo: repo, - state: 'open' - }) - //console.log(pulls) - for (let pr of pulls.data) { - const p: IPullrequest = { - html_url: pr.html_url, - number: pr.number, - title: pr.title, - state: pr.state, - draft: pr.draft, - user: { - login: pr.user.login, - avatar_url: pr.user.avatar_url, - }, - created_at: pr.created_at, - updated_at: pr.updated_at, - closed_at: pr.closed_at, - merged_at: pr.merged_at, - locked: pr.locked, - branch: pr.head.ref, - ssh_url: pr.head.repo.ssh_url, - } - ret.push(p) - } - } catch (error) { - this.logger.log(error) - } + try { + const commits = await this.octokit.request( + 'GET /repos/{owner}/{repo}/commits', + { + owner: owner, + repo: repo, + }, + ); + for (const commit of commits.data) { + ret.push(commit.sha); + } + } catch (error) { + this.logger.log(error); + } - return ret; + return ret; + } + + public async getPullrequests(gitrepo: string): Promise { + const ret: IPullrequest[] = []; + + const { repo, owner } = this.parseRepo(gitrepo); + + try { + const pulls = await this.octokit.request( + 'GET /repos/{owner}/{repo}/pulls', + { + owner: owner, + repo: repo, + state: 'open', + }, + ); + //console.log(pulls) + for (const pr of pulls.data) { + const p: IPullrequest = { + html_url: pr.html_url, + number: pr.number, + title: pr.title, + state: pr.state, + draft: pr.draft, + user: { + login: pr.user.login, + avatar_url: pr.user.avatar_url, + }, + created_at: pr.created_at, + updated_at: pr.updated_at, + closed_at: pr.closed_at, + merged_at: pr.merged_at, + locked: pr.locked, + branch: pr.head.ref, + ssh_url: pr.head.repo.ssh_url, + }; + ret.push(p); + } + } catch (error) { + this.logger.log(error); } -} \ No newline at end of file + + return ret; + } +} diff --git a/server-refactored-v3/src/repo/git/gitlab.ts b/server-refactored-v3/src/repo/git/gitlab.ts index 95cf19d5..1bc64033 100644 --- a/server-refactored-v3/src/repo/git/gitlab.ts +++ b/server-refactored-v3/src/repo/git/gitlab.ts @@ -1,379 +1,395 @@ // https://www.nerd.vision/post/nerdvision-gitlab-js-an-easier-way-to-access-the-gitlab-api-in-javascript // https://www.npmjs.com/package/@nerdvision/gitlab-js import debug from 'debug'; -import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import { + IWebhook, + IRepository, + IWebhookR, + IDeploykeyR, + IPullrequest, +} from './types'; import { Repo } from './repo'; -import {Client as GitlabClient} from '@nerdvision/gitlab-js'; -import {Options} from 'got'; -import gitUrlParse = require("git-url-parse"); +import { Client as GitlabClient } from '@nerdvision/gitlab-js'; +import { Options } from 'got'; +import gitUrlParse = require('git-url-parse'); import { Logger } from '@nestjs/common'; - export class GitlabApi extends Repo { - private gitlab: GitlabClient; - private opt = { - headers: { - 'Content-Type': 'application/json', - }, - } as Options; - - constructor(baseURL: string, token: string) { - super("gitlab"); - const host = baseURL || 'https://gitlab.com'; - - if (token == undefined) { - Logger.log('☑️ Feature: Gitlab not configured (no token)', 'Feature'); - } else { - Logger.log('✅ Feature: Gitlab configured: '+host, 'Feature'); - } - - this.gitlab = new GitlabClient({ - token: token, - host: host, - }); + private gitlab: GitlabClient; + private opt = { + headers: { + 'Content-Type': 'application/json', + }, + } as Options; + + constructor(baseURL: string, token: string) { + super('gitlab'); + const host = baseURL || 'https://gitlab.com'; + + if (token == undefined) { + Logger.log('☑️ Feature: Gitlab not configured (no token)', 'Feature'); + } else { + Logger.log('✅ Feature: Gitlab configured: ' + host, 'Feature'); } - protected async getRepository(gitrepo: string): Promise { - //https://docs.gitlab.com/ee/api/projects.html - let ret: IRepository = { - status: 500, - statusText: 'error', - data: { - owner: 'unknown', - name: 'unknown', - admin: false, - push: false, - } - } - - let parsed = gitUrlParse(gitrepo) - let repo = parsed.name - let owner = parsed.owner - - let res: any = await this.gitlab.get(`projects/${owner}%2F${repo}`) - .catch((error: any) => { - console.log(error) - return ret; - }) - //console.log(res) - - res.private = false; - if (res.visibility === 'private') { - res.private = true; - } + this.gitlab = new GitlabClient({ + token: token, + host: host, + }); + } + + protected async getRepository(gitrepo: string): Promise { + //https://docs.gitlab.com/ee/api/projects.html + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + }, + }; + + const parsed = gitUrlParse(gitrepo); + const repo = parsed.name; + const owner = parsed.owner; + + const res: any = await this.gitlab + .get(`projects/${owner}%2F${repo}`) + .catch((error: any) => { + console.log(error); + return ret; + }); + //console.log(res) - // TODO: this is a workaround since the information is not available - res.permissions.admin = true; - res.permissions.push = true; + res.private = false; + if (res.visibility === 'private') { + res.private = true; + } + // TODO: this is a workaround since the information is not available + res.permissions.admin = true; + res.permissions.push = true; + + ret = { + status: 200, + statusText: 'found', + data: { + id: res.id, + node_id: res.path_with_namespace, + name: res.path, + description: res.description, + owner: res.namespace.path, + private: res.private, + ssh_url: res.ssh_url_to_repo, + language: res.language, + homepage: res.namespace.web_url, + admin: res.permissions.admin, + push: res.permissions.push, + visibility: res.visibility, + default_branch: res.default_branch, + }, + }; + return ret; + } + + protected async addWebhook( + owner: string, + repo: string, + url: string, + secret: string, + ): Promise { + // https://docs.gitlab.com/ee/api/projects.html#list-project-hooks + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + }, + }; + + const webhooksList: any = await this.gitlab + .get(`projects/${owner}%2F${repo}/hooks`) + .catch((error: any) => { + console.log(error); + return ret; + }); + // try to find the webhook + for (const webhook of webhooksList) { + if (webhook.url === url && webhook.disabled_until === null) { ret = { - status: 200, - statusText: 'found', - data: { - id: res.id, - node_id: res.path_with_namespace, - name: res.path, - description: res.description, - owner: res.namespace.path, - private : res.private, - ssh_url: res.ssh_url_to_repo, - language: res.language, - homepage: res.namespace.web_url, - admin: res.permissions.admin, - push: res.permissions.push, - visibility: res.visibility, - default_branch: res.default_branch, - } - } + status: 422, + statusText: 'found', + data: { + id: webhook.id, + active: true, + created_at: webhook.created_at, + url: webhook.url, + insecure: false, //TODO use the inverted enable_ssl_verification field + events: ['pull_request', 'push'], + }, + }; return ret; - + } } + // create the webhook since it does not exist + try { + const res: any = await this.gitlab.post( + `projects/${owner}%2F${repo}/hooks`, + JSON.stringify({ + url: url, + token: secret, + merge_requests_events: true, + push_events: true, + }), + undefined, + this.opt, + ); + + ret = { + status: 201, + statusText: 'created', + data: { + id: res.id, + active: res.active, + created_at: res.created_at, + url: res.url, + insecure: false, + events: ['pull_request', 'push'], + }, + }; + } catch (e) { + console.log('Failed to create Webhook'); + console.log(e); + } + return ret; + } + + async addDeployKey(owner: string, repo: string): Promise { + const keyPair = this.createDeployKeyPair(); + + const title: string = 'bot@kubero.' + Date.now(); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: title, + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + try { + // https://docs.gitlab.com/ee/api/deploy_keys.html#add-deploy-key + const res: any = await this.gitlab.post( + `projects/${owner}%2F${repo}/deploy_keys`, + JSON.stringify({ + title: title, + key: keyPair.pubKey, + can_push: false, + }), + undefined, + this.opt, + ); + + console.log(res); + + ret = { + status: 201, + statusText: 'created', + data: { + id: res.id, + title: res.title, + verified: res.verified, + created_at: res.created_at, + url: res.url, + read_only: res.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + } catch (e) { + console.log(e); + } - protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { - // https://docs.gitlab.com/ee/api/projects.html#list-project-hooks - let ret: IWebhookR = { - status: 500, - statusText: 'error', - data: { - id: 0, - active: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - insecure: true, - events: [], - } - } - - const webhooksList: any = await this.gitlab.get(`projects/${owner}%2F${repo}/hooks`) - .catch((error: any) => { - console.log(error) - return ret; - }) - // try to find the webhook - for (let webhook of webhooksList) { - if (webhook.url === url && - webhook.disabled_until === null) { - ret = { - status: 422, - statusText: 'found', - data: { - id: webhook.id, - active: true, - created_at: webhook.created_at, - url: webhook.url, - insecure: false, //TODO use the inverted enable_ssl_verification field - events: ["pull_request", "push"], - } - } - return ret; - } - } - - // create the webhook since it does not exist - try { - let res: any = await this.gitlab.post(`projects/${owner}%2F${repo}/hooks`, JSON.stringify({ - url: url, - token: secret, - merge_requests_events: true, - push_events: true, - }), - undefined, - this.opt, - ); - - ret = { - status: 201, - statusText: 'created', - data: { - id: res.id, - active: res.active, - created_at: res.created_at, - url: res.url, - insecure: false, - events: ["pull_request", "push"], - } - } - } catch (e) { - console.log("Failed to create Webhook") - console.log(e) - } - return ret; + return ret; + } + + public getWebhook( + event: string, + delivery: string, + token: string, + body: any, + ): IWebhook | boolean { + const secret = process.env.KUBERO_WEBHOOK_SECRET as string; + + let verified = false; + if (secret === token) { + debug.debug('Gitlab webhook signature is valid for event: ' + delivery); + verified = true; + } else { + this.logger.log('ERROR: invalid token/secret for event: ' + delivery); + this.logger.log('Secret: ' + secret); + this.logger.log('Token : ' + token); + verified = false; + return false; } - async addDeployKey(owner: string, repo: string): Promise { - - const keyPair = this.createDeployKeyPair(); - - const title: string = "bot@kubero."+Date.now(); - - let ret: IDeploykeyR = { - status: 500, - statusText: 'error', - data: { - id: 0, - title: title, - verified: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - read_only: true, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - try { - // https://docs.gitlab.com/ee/api/deploy_keys.html#add-deploy-key - let res:any = await this.gitlab.post(`projects/${owner}%2F${repo}/deploy_keys`, JSON.stringify({ - title: title, - key: keyPair.pubKey, - can_push: false - }), - undefined, - this.opt, - ); - - console.log(res) - - ret = { - status: 201, - statusText: 'created', - data: { - id: res.id, - title: res.title, - verified: res.verified, - created_at: res.created_at, - url: res.url, - read_only: res.read_only, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - } catch (e) { - console.log(e) - } - - return ret + // use github and gitea naming for the event + let github_event = event; + if (event === 'Push Hook') { + github_event = 'push'; + } else if (event === 'Merge Request Hook') { + github_event = 'pull_request'; + } else { + this.logger.log('ERROR: unknown event: ' + event); + return false; } - public getWebhook(event: string, delivery: string, token: string, body: any): IWebhook | boolean { - let secret = process.env.KUBERO_WEBHOOK_SECRET as string; - - let verified = false; - if (secret === token) { - debug.debug('Gitlab webhook signature is valid for event: '+delivery); - verified = true; - } else { - this.logger.log('ERROR: invalid token/secret for event: '+delivery); - this.logger.log('Secret: '+secret); - this.logger.log('Token : '+token); - verified = false; - return false; - } - - // use github and gitea naming for the event - let github_event = event; - if (event === 'Push Hook') { - github_event = 'push'; - } else if (event === 'Merge Request Hook') { - github_event = 'pull_request'; - } else { - this.logger.log('ERROR: unknown event: '+event); - return false; - } - - - let branch: string = 'main'; - let ssh_url: string = ''; - let action; - if (body.ref != undefined) { - let ref = body.ref - let refs = ref.split('/') - branch = refs[refs.length - 1] - ssh_url = body.project.git_ssh_url - } else if (body.pull_request != undefined) { - action = body.action, - branch = body.pull_request.head.ref - ssh_url = body.pull_request.head.repo.ssh_url - } else { - ssh_url = body.project.git_ssh_url - } - - try { - let webhook: IWebhook = { - repoprovider: 'gitlab', - action: action, - event: github_event, - delivery: delivery, - body: body, - branch: branch, - verified: verified, - repo: { - ssh_url: ssh_url, - } - } - - return webhook; - } catch (error) { - this.logger.log(error) - return false; - } + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.ref != undefined) { + const ref = body.ref; + const refs = ref.split('/'); + branch = refs[refs.length - 1]; + ssh_url = body.project.git_ssh_url; + } else if (body.pull_request != undefined) { + (action = body.action), (branch = body.pull_request.head.ref); + ssh_url = body.pull_request.head.repo.ssh_url; + } else { + ssh_url = body.project.git_ssh_url; } - public async listRepos(): Promise { - let ret: string[] = []; - const repos:any = await this.gitlab.get('projects', { membership: true }) - .catch((error: any) => { - console.log(error) - return ret; - }) + try { + const webhook: IWebhook = { + repoprovider: 'gitlab', + action: action, + event: github_event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + }, + }; - for (let repo of repos) { - ret.push(repo.ssh_url_to_repo) - } + return webhook; + } catch (error) { + this.logger.log(error); + return false; + } + } + + public async listRepos(): Promise { + const ret: string[] = []; + const repos: any = await this.gitlab + .get('projects', { membership: true }) + .catch((error: any) => { + console.log(error); return ret; + }); + + for (const repo of repos) { + ret.push(repo.ssh_url_to_repo); } + return ret; + } - public async getBranches(gitrepo: string): Promise{ - // https://docs.gitlab.com/ee/api/branches.html#list-repository-branches - // not implemented yet - let ret: string[] = []; + public async getBranches(gitrepo: string): Promise { + // https://docs.gitlab.com/ee/api/branches.html#list-repository-branches + // not implemented yet + const ret: string[] = []; - let {repo, owner} = this.parseRepo(gitrepo) + const { repo, owner } = this.parseRepo(gitrepo); - try { - const branches:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/branches`) - .catch((error: any) => { - console.log(error) - return ret; - }) + try { + const branches: any = await this.gitlab + .get(`projects/${owner}%2F${repo}/repository/branches`) + .catch((error: any) => { + console.log(error); + return ret; + }); - for (let branch of branches) { - ret.push(branch.name) - } - } catch (error) { - console.log(error) - } + for (const branch of branches) { + ret.push(branch.name); + } + } catch (error) { + console.log(error); + } + return ret; + } - return ret; - } + public async getReferences(gitrepo: string): Promise { + const ret: string[] = []; - public async getReferences(gitrepo: string): Promise{ - let ret: string[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const branches:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/branches`) - .catch((error: any) => { - console.log(error) - return ret; - }) - - for (let branch of branches) { - ret.push(branch.name) - } - } catch (error) { - console.log(error) - } - - try { - const tags:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/tags`) - .catch((error: any) => { - console.log(error) - return ret; - }) - - for (let tag of tags) { - ret.push(tag.name) - } - } catch (error) { - console.log(error) - } - - try { - const commits:any = await this.gitlab.get(`projects/${owner}%2F${repo}/repository/commits`) - .catch((error: any) => { - console.log(error) - return ret; - }) - - for (let commit of commits) { - ret.push(commit.id) - } - } catch (error) { - console.log(error) - } + const { repo, owner } = this.parseRepo(gitrepo); - return ret; + try { + const branches: any = await this.gitlab + .get(`projects/${owner}%2F${repo}/repository/branches`) + .catch((error: any) => { + console.log(error); + return ret; + }); + + for (const branch of branches) { + ret.push(branch.name); + } + } catch (error) { + console.log(error); } - public async getPullrequests(gitrepo: string): Promise{ + try { + const tags: any = await this.gitlab + .get(`projects/${owner}%2F${repo}/repository/tags`) + .catch((error: any) => { + console.log(error); + return ret; + }); - let ret: IPullrequest[] = []; + for (const tag of tags) { + ret.push(tag.name); + } + } catch (error) { + console.log(error); + } + try { + const commits: any = await this.gitlab + .get(`projects/${owner}%2F${repo}/repository/commits`) + .catch((error: any) => { + console.log(error); + return ret; + }); - return ret; + for (const commit of commits) { + ret.push(commit.id); + } + } catch (error) { + console.log(error); } -} \ No newline at end of file + return ret; + } + + public async getPullrequests(gitrepo: string): Promise { + const ret: IPullrequest[] = []; + + return ret; + } +} diff --git a/server-refactored-v3/src/repo/git/gogs.ts b/server-refactored-v3/src/repo/git/gogs.ts index de726d25..560a917b 100644 --- a/server-refactored-v3/src/repo/git/gogs.ts +++ b/server-refactored-v3/src/repo/git/gogs.ts @@ -1,331 +1,344 @@ import debug from 'debug'; -import * as crypto from "crypto" -import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; +import * as crypto from 'crypto'; +import { + IWebhook, + IRepository, + IWebhookR, + IDeploykeyR, + IPullrequest, +} from './types'; import { Repo } from './repo'; -import gitUrlParse = require("git-url-parse"); -debug('app:kubero:gogs:api') +import gitUrlParse = require('git-url-parse'); +debug('app:kubero:gogs:api'); //https://www.npmjs.com/package/gitea-js -import { giteaApi, Api } from "gitea-js" +import { giteaApi, Api } from 'gitea-js'; import { fetch as fetchGitea } from 'cross-fetch'; export class GogsApi extends Repo { - private gitea: any; - - constructor(baseURL: string, token: string) { - super("gogs"); - this.gitea = giteaApi(baseURL, { - token: token, - customFetch: fetchGitea, - }); + private gitea: any; + + constructor(baseURL: string, token: string) { + super('gogs'); + this.gitea = giteaApi(baseURL, { + token: token, + customFetch: fetchGitea, + }); + } + + protected async getRepository(gitrepo: string): Promise { + const GitUrlParse = require('git-url-parse'); + + let ret: IRepository = { + status: 500, + statusText: 'error', + data: { + owner: 'unknown', + name: 'unknown', + admin: false, + push: false, + }, + }; + + const parsed = gitUrlParse(gitrepo); + const repo = parsed.name; + const owner = parsed.owner; + + if (owner == undefined) { + this.logger.log('git owner extraction failed'); + throw new Error('git owner extraction failed'); + } + if (repo == undefined) { + this.logger.log('git owner extraction failed'); + throw new Error('git repo extraction failed'); } - protected async getRepository(gitrepo: string): Promise { - const GitUrlParse = require("git-url-parse"); - - let ret: IRepository = { - status: 500, - statusText: 'error', - data: { - owner: 'unknown', - name: 'unknown', - admin: false, - push: false, - } - } - - let parsed = gitUrlParse(gitrepo) - let repo = parsed.name - let owner = parsed.owner - - if ( owner == undefined ){ - this.logger.log("git owner extraction failed"); - throw new Error("git owner extraction failed"); - } - if ( repo == undefined ){ - this.logger.log("git owner extraction failed"); - throw new Error("git repo extraction failed"); - } - - let res = await this.gitea.repos.repoGet(owner, repo) - .catch((error: any) => { - console.log(error) - return ret; - }) - + const res = await this.gitea.repos + .repoGet(owner, repo) + .catch((error: any) => { + console.log(error); + return ret; + }); + + ret = { + status: res.status, + statusText: 'found', + data: { + id: res.data.id, + node_id: res.data.node_id, + name: res.data.name, + description: res.data.description, + owner: res.data.owner.login, + private: res.data.private, + ssh_url: res.data.ssh_url, + language: res.data.language, + homepage: res.data.homepage, + admin: res.data.permissions.admin, + push: res.data.permissions.push, + visibility: res.data.visibility, + default_branch: res.data.default_branch, + }, + }; + return ret; + } + + protected async addWebhook( + owner: string, + repo: string, + url: string, + secret: string, + ): Promise { + let ret: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + }, + }; + + //https://try.gitea.io/api/swagger#/repository/repoListHooks + const webhooksList = await this.gitea.repos + .repoListHooks(owner, repo) + .catch((error: any) => { + console.log(error); + return ret; + }); + + // try to find the webhook + for (const webhook of webhooksList.data) { + if ( + webhook.config.url === url && + webhook.config.content_type === 'json' && + webhook.active === true + ) { ret = { - status: res.status, - statusText: 'found', - data: { - id: res.data.id, - node_id: res.data.node_id, - name: res.data.name, - description: res.data.description, - owner: res.data.owner.login, - private : res.data.private, - ssh_url: res.data.ssh_url, - language: res.data.language, - homepage: res.data.homepage, - admin: res.data.permissions.admin, - push: res.data.permissions.push, - visibility: res.data.visibility, - default_branch: res.data.default_branch, - } - } + status: 422, + statusText: 'found', + data: webhook, + }; return ret; - + } } - - protected async addWebhook(owner: string, repo: string, url: string, secret: string): Promise { - - let ret: IWebhookR = { - status: 500, - statusText: 'error', - data: { - id: 0, - active: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - insecure: true, - events: [], - } - } - - //https://try.gitea.io/api/swagger#/repository/repoListHooks - const webhooksList = await this.gitea.repos.repoListHooks(owner, repo) - .catch((error: any) => { - console.log(error) - return ret; - }) - - // try to find the webhook - for (let webhook of webhooksList.data) { - if (webhook.config.url === url && - webhook.config.content_type === 'json' && - webhook.active === true) { - ret = { - status: 422, - statusText: 'found', - data: webhook, - } - return ret; - } - } - //console.log(webhooksList) - - // create the webhook since it does not exist - try { - - //https://try.gitea.io/api/swagger#/repository/repoCreateHook - let res = await this.gitea.repos.repoCreateHook(owner, repo, { - active: true, - config: { - url: url, - content_type: "json", - secret: secret, - insecure_ssl: '0' - }, - events: [ - "push", - "pull_request" - ], - type: "gogs" - }); - - ret = { - status: res.status, - statusText: 'created', - data: { - id: res.data.id, - active: res.data.active, - created_at: res.data.created_at, - url: res.data.url, - insecure: res.data.config.insecure_ssl, - events: res.data.events, - } - } - } catch (e) { - console.log(e) - } - return ret; + //console.log(webhooksList) + + // create the webhook since it does not exist + try { + //https://try.gitea.io/api/swagger#/repository/repoCreateHook + const res = await this.gitea.repos.repoCreateHook(owner, repo, { + active: true, + config: { + url: url, + content_type: 'json', + secret: secret, + insecure_ssl: '0', + }, + events: ['push', 'pull_request'], + type: 'gogs', + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + active: res.data.active, + created_at: res.data.created_at, + url: res.data.url, + insecure: res.data.config.insecure_ssl, + events: res.data.events, + }, + }; + } catch (e) { + console.log(e); + } + return ret; + } + + protected async addDeployKey( + owner: string, + repo: string, + ): Promise { + const keyPair = this.createDeployKeyPair(); + + const title: string = 'bot@kubero.' + crypto.randomBytes(4).toString('hex'); + + let ret: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: title, + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + try { + //https://try.gitea.io/api/swagger#/repository/repoCreateKey + const res = await this.gitea.repos.repoCreateKey(owner, repo, { + title: title, + key: keyPair.pubKey, + read_only: true, + }); + + ret = { + status: res.status, + statusText: 'created', + data: { + id: res.data.id, + title: res.data.title, + verified: res.data.verified, + created_at: res.data.created_at, + url: res.data.url, + read_only: res.data.read_only, + pub: keyPair.pubKeyBase64, + priv: keyPair.privKeyBase64, + }, + }; + } catch (e) { + console.log(e); } - - protected async addDeployKey(owner: string, repo: string): Promise { - - const keyPair = this.createDeployKeyPair(); - - const title: string = "bot@kubero."+crypto.randomBytes(4).toString('hex'); - - let ret: IDeploykeyR = { - status: 500, - statusText: 'error', - data: { - id: 0, - title: title, - verified: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - read_only: true, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - try { - //https://try.gitea.io/api/swagger#/repository/repoCreateKey - let res = await this.gitea.repos.repoCreateKey(owner, repo, { - title: title, - key: keyPair.pubKey, - read_only: true - }); - - ret = { - status: res.status, - statusText: 'created', - data: { - id: res.data.id, - title: res.data.title, - verified: res.data.verified, - created_at: res.data.created_at, - url: res.data.url, - read_only: res.data.read_only, - pub: keyPair.pubKeyBase64, - priv: keyPair.privKeyBase64 - } - } - } catch (e) { - console.log(e) - } - - return ret + return ret; + } + + public getWebhook( + event: string, + delivery: string, + signature: string, + body: any, + ): IWebhook | boolean { + const secret = process.env.KUBERO_WEBHOOK_SECRET as string; + const hash = crypto + .createHmac('sha256', secret) + .update(JSON.stringify(body, null, ' ')) + .digest('hex'); + + let verified = false; + if (hash === signature) { + debug.debug('Gogs webhook signature is valid for event: ' + delivery); + verified = true; + } else { + this.logger.log('ERROR: invalid signature for event: ' + delivery); + this.logger.log('Hash: ' + hash); + this.logger.log('Signature: ' + signature); + verified = false; + return false; } - public getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean { - let secret = process.env.KUBERO_WEBHOOK_SECRET as string; - let hash = crypto.createHmac('sha256', secret).update(JSON.stringify(body, null, ' ')).digest('hex') - - let verified = false; - if (hash === signature) { - debug.debug('Gogs webhook signature is valid for event: '+delivery); - verified = true; - } else { - this.logger.log('ERROR: invalid signature for event: '+delivery); - this.logger.log('Hash: '+hash); - this.logger.log('Signature: '+signature); - verified = false; - return false; - } - - let branch: string = 'main'; - let ssh_url: string = ''; - let action; - if (body.pull_request == undefined) { - let ref = body.ref - let refs = ref.split('/') - branch = refs[refs.length - 1] - ssh_url = body.repository.ssh_url - } else if (body.pull_request != undefined) { - action = body.action, - branch = body.pull_request.head.ref - ssh_url = body.pull_request.head.repo.ssh_url - } else { - ssh_url = body.repository.ssh_url - } - - try { - let webhook: IWebhook = { - repoprovider: 'gogs', - action: action, - event: event, - delivery: delivery, - body: body, - branch: branch, - verified: verified, - repo: { - ssh_url: ssh_url, - } - } - - return webhook; - } catch (error) { - console.log(error) - return false; - } + let branch: string = 'main'; + let ssh_url: string = ''; + let action; + if (body.pull_request == undefined) { + const ref = body.ref; + const refs = ref.split('/'); + branch = refs[refs.length - 1]; + ssh_url = body.repository.ssh_url; + } else if (body.pull_request != undefined) { + (action = body.action), (branch = body.pull_request.head.ref); + ssh_url = body.pull_request.head.repo.ssh_url; + } else { + ssh_url = body.repository.ssh_url; } - public async listRepos(): Promise { - let ret: string[] = []; - try { - const repos = await this.gitea.user.userCurrentListRepos() - for (let repo of repos.data) { - ret.push(repo.ssh_url) - } - } catch (error) { - console.log(error) - } - return ret; + try { + const webhook: IWebhook = { + repoprovider: 'gogs', + action: action, + event: event, + delivery: delivery, + body: body, + branch: branch, + verified: verified, + repo: { + ssh_url: ssh_url, + }, + }; + + return webhook; + } catch (error) { + console.log(error); + return false; + } + } + + public async listRepos(): Promise { + const ret: string[] = []; + try { + const repos = await this.gitea.user.userCurrentListRepos(); + for (const repo of repos.data) { + ret.push(repo.ssh_url); + } + } catch (error) { + console.log(error); + } + return ret; + } + + public async getBranches(gitrepo: string): Promise { + // https://try.gitea.io/api/swagger#/repository/repoListBranches + const ret: string[] = []; + + const { repo, owner } = this.parseRepo(gitrepo); + + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo); + for (const branch of branches.data) { + ret.push(branch.name); + } + } catch (error) { + console.log(error); } - public async getBranches(gitrepo: string): Promise{ - // https://try.gitea.io/api/swagger#/repository/repoListBranches - let ret: string[] = []; + return ret; + } - let {repo, owner} = this.parseRepo(gitrepo) + public async getReferences(gitrepo: string): Promise { + const ret: string[] = []; - try { - const branches = await this.gitea.repos.repoListBranches(owner, repo) - for (let branch of branches.data) { - ret.push(branch.name) - } - } catch (error) { - console.log(error) - } + const { repo, owner } = this.parseRepo(gitrepo); - return ret; + try { + const branches = await this.gitea.repos.repoListBranches(owner, repo); + for (const branch of branches.data) { + ret.push(branch.name); + } + } catch (error) { + this.logger.log(error); } - public async getReferences(gitrepo: string): Promise{ - - let ret: string[] = []; - - let {repo, owner} = this.parseRepo(gitrepo) - - try { - const branches = await this.gitea.repos.repoListBranches(owner, repo) - for (let branch of branches.data) { - ret.push(branch.name) - } - } catch (error) { - this.logger.log(error) - } - - try { - const tags = await this.gitea.repos.repoListTags(owner, repo) - for (let tag of tags.data) { - ret.push(tag.name) - } - } catch (error) { - this.logger.log(error) - } - - try { - const commits = await this.gitea.repos.repoListCommits(owner, repo) - for (let commit of commits.data) { - ret.push(commit.sha) - } - } catch (error) { - this.logger.log(error) - } - - return ret; - + try { + const tags = await this.gitea.repos.repoListTags(owner, repo); + for (const tag of tags.data) { + ret.push(tag.name); + } + } catch (error) { + this.logger.log(error); } - public async getPullrequests(gitrepo: string): Promise{ + try { + const commits = await this.gitea.repos.repoListCommits(owner, repo); + for (const commit of commits.data) { + ret.push(commit.sha); + } + } catch (error) { + this.logger.log(error); + } - let ret: IPullrequest[] = []; + return ret; + } + public async getPullrequests(gitrepo: string): Promise { + const ret: IPullrequest[] = []; - return ret; - } + return ret; + } } diff --git a/server-refactored-v3/src/repo/git/repo.test.ts b/server-refactored-v3/src/repo/git/repo.test.ts index 012d42db..bb23c3a9 100644 --- a/server-refactored-v3/src/repo/git/repo.test.ts +++ b/server-refactored-v3/src/repo/git/repo.test.ts @@ -5,36 +5,36 @@ import { BitbucketApi } from './bitbucket'; import { GiteaApi } from './gitea'; describe('GithubApi', () => { - it('should load config', () => { - const github = new GithubApi("token"); - expect(github).toBeTruthy(); - }); + it('should load config', () => { + const github = new GithubApi('token'); + expect(github).toBeTruthy(); + }); }); describe('GogsApi', () => { - it('should load config', () => { - const gogs = new GogsApi("http://localhost:3000", "token"); - expect(gogs).toBeTruthy(); - }); + it('should load config', () => { + const gogs = new GogsApi('http://localhost:3000', 'token'); + expect(gogs).toBeTruthy(); + }); }); describe('GitlabApi', () => { - it('should load config', () => { - const gitlab = new GitlabApi("https://gitlab.com", "token"); - expect(gitlab).toBeTruthy(); - }); + it('should load config', () => { + const gitlab = new GitlabApi('https://gitlab.com', 'token'); + expect(gitlab).toBeTruthy(); + }); }); describe('GiteaApi', () => { - it('should load config', () => { - const gitea = new GiteaApi("https://codeberg.org", "token"); - expect(gitea).toBeTruthy(); - }); + it('should load config', () => { + const gitea = new GiteaApi('https://codeberg.org', 'token'); + expect(gitea).toBeTruthy(); + }); }); describe('Bitbucket', () => { - it('should load config', () => { - const bitbucket = new BitbucketApi("username", "password"); - expect(bitbucket).toBeTruthy(); - }); -}); \ No newline at end of file + it('should load config', () => { + const bitbucket = new BitbucketApi('username', 'password'); + expect(bitbucket).toBeTruthy(); + }); +}); diff --git a/server-refactored-v3/src/repo/git/repo.ts b/server-refactored-v3/src/repo/git/repo.ts index d2d0367b..d3903245 100644 --- a/server-refactored-v3/src/repo/git/repo.ts +++ b/server-refactored-v3/src/repo/git/repo.ts @@ -1,136 +1,165 @@ -import { Logger } from "@nestjs/common"; -import * as crypto from "crypto" -import { IWebhook, IRepository, IWebhookR, IDeploykeyR, IPullrequest} from './types'; -import { IDeployKeyPair} from '../repo.interface'; +import { Logger } from '@nestjs/common'; +import * as crypto from 'crypto'; +import { + IWebhook, + IRepository, + IWebhookR, + IDeploykeyR, + IPullrequest, +} from './types'; +import { IDeployKeyPair } from '../repo.interface'; export abstract class Repo { - - protected repoProvider: string; - protected logger = new Logger(Repo.name); - protected sshpk = require('sshpk'); - - constructor(repoProvider: string) { - this.repoProvider = repoProvider; - } - - protected createDeployKeyPair(): IDeployKeyPair{ - this.logger.debug('createDeployKeyPair'); - - const keyPair = crypto.generateKeyPairSync('ed25519', { - //modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - //cipher: 'aes-256-cbc', - //passphrase: '' - } - }); - //this.logger.debug(JSON.stringify(keyPair)); - - const pubKeySsh = this.sshpk.parseKey(keyPair.publicKey, 'pem'); - const pubKeySshString = pubKeySsh.toString('ssh'); - const fingerprint = pubKeySsh.fingerprint('sha256').toString('hex'); - this.logger.debug(pubKeySshString); - - const privKeySsh = this.sshpk.parsePrivateKey(keyPair.privateKey, 'pem'); - const privKeySshString = privKeySsh.toString('ssh'); - //this.logger.debug(privKeySshString); - - return { - fingerprint: fingerprint, - pubKey: pubKeySshString, - pubKeyBase64: Buffer.from(pubKeySshString).toString('base64'), - privKey: privKeySshString, - privKeyBase64: Buffer.from(privKeySshString).toString('base64') - }; + protected repoProvider: string; + protected logger = new Logger(Repo.name); + protected sshpk = require('sshpk'); + + constructor(repoProvider: string) { + this.repoProvider = repoProvider; + } + + protected createDeployKeyPair(): IDeployKeyPair { + this.logger.debug('createDeployKeyPair'); + + const keyPair = crypto.generateKeyPairSync('ed25519', { + //modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem', + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + //cipher: 'aes-256-cbc', + //passphrase: '' + }, + }); + //this.logger.debug(JSON.stringify(keyPair)); + + const pubKeySsh = this.sshpk.parseKey(keyPair.publicKey, 'pem'); + const pubKeySshString = pubKeySsh.toString('ssh'); + const fingerprint = pubKeySsh.fingerprint('sha256').toString('hex'); + this.logger.debug(pubKeySshString); + + const privKeySsh = this.sshpk.parsePrivateKey(keyPair.privateKey, 'pem'); + const privKeySshString = privKeySsh.toString('ssh'); + //this.logger.debug(privKeySshString); + + return { + fingerprint: fingerprint, + pubKey: pubKeySshString, + pubKeyBase64: Buffer.from(pubKeySshString).toString('base64'), + privKey: privKeySshString, + privKeyBase64: Buffer.from(privKeySshString).toString('base64'), + }; + } + + public async connectRepo(gitrepo: string): Promise<{ + keys: IDeploykeyR | undefined; + repository: IRepository; + webhook: IWebhookR | undefined; + }> { + this.logger.log('connectPipeline: ' + gitrepo); + + if (process.env.KUBERO_WEBHOOK_SECRET == undefined) { + this.logger.log('KUBERO_WEBHOOK_SECRET is not defined'); + throw new Error('KUBERO_WEBHOOK_SECRET is not defined'); } - - public async connectRepo(gitrepo: string): Promise<{keys: IDeploykeyR | undefined, repository: IRepository, webhook: IWebhookR | undefined}> { - this.logger.log('connectPipeline: '+gitrepo); - - if (process.env.KUBERO_WEBHOOK_SECRET == undefined) { - this.logger.log("KUBERO_WEBHOOK_SECRET is not defined") - throw new Error("KUBERO_WEBHOOK_SECRET is not defined"); - } - if (process.env.KUBERO_WEBHOOK_URL == undefined) { - this.logger.log("KUBERO_WEBHOOK_URL is not defined") - throw new Error("KUBERO_WEBHOOK_URL is not defined"); - } - - const repository = await this.getRepository(gitrepo) - //console.debug(repository); - - let keys: IDeploykeyR = { - status: 500, - statusText: 'error', - data: { - id: 0, - title: "bot@kubero", - verified: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - read_only: true, - pub: '', - priv: '', - } - } - let webhook: IWebhookR = { - status: 500, - statusText: 'error', - data: { - id: 0, - active: false, - created_at: '2020-01-01T00:00:00Z', - url: '', - insecure: true, - events: [], - } - } - if (repository.status == 200 && repository.data.admin == true) { - - webhook = await this.addWebhook( - repository.data.owner, - repository.data.name, - process.env.KUBERO_WEBHOOK_URL+'/'+this.repoProvider, - process.env.KUBERO_WEBHOOK_SECRET, - ); - - keys = await this.addDeployKey(repository.data.owner, repository.data.name); - } - - return {keys: keys, repository: repository, webhook: webhook}; - - } - - public async disconnectRepo(gitrepo: string): Promise { - this.logger.log('disconnectPipeline: '+gitrepo); - - const {owner, repo} = this.parseRepo(gitrepo); - - // TODO: implement remove deploy key and webhook for all providers - //this.removeDeployKey(owner, repo, 0); - //this.removeWebhook(owner, repo, 0); - - return true; + if (process.env.KUBERO_WEBHOOK_URL == undefined) { + this.logger.log('KUBERO_WEBHOOK_URL is not defined'); + throw new Error('KUBERO_WEBHOOK_URL is not defined'); } - protected parseRepo(gitrepo: string): {owner: string, repo: string} { - let owner = gitrepo.match(/^git@.{0,100}:(.{0,100})\/.{0,100}$/)?.[1] as string; - let repo = gitrepo.match(/^git@.{0,100}:.{0,100}\/(.{0,100}).git$/)?.[1] as string; - return { owner: owner, repo: repo }; + const repository = await this.getRepository(gitrepo); + //console.debug(repository); + + let keys: IDeploykeyR = { + status: 500, + statusText: 'error', + data: { + id: 0, + title: 'bot@kubero', + verified: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + read_only: true, + pub: '', + priv: '', + }, + }; + let webhook: IWebhookR = { + status: 500, + statusText: 'error', + data: { + id: 0, + active: false, + created_at: '2020-01-01T00:00:00Z', + url: '', + insecure: true, + events: [], + }, + }; + if (repository.status == 200 && repository.data.admin == true) { + webhook = await this.addWebhook( + repository.data.owner, + repository.data.name, + process.env.KUBERO_WEBHOOK_URL + '/' + this.repoProvider, + process.env.KUBERO_WEBHOOK_SECRET, + ); + + keys = await this.addDeployKey( + repository.data.owner, + repository.data.name, + ); } - protected abstract addDeployKey(owner: string, repo: string): Promise - //protected abstract removeDeployKey(owner: string, repo: string, id: number): Promise - protected abstract getRepository(gitrepo: string): Promise; - protected abstract addWebhook(owner: string, repo: string, url: string, secret: string): Promise; - protected abstract getWebhook(event: string, delivery: string, signature: string, body: any): IWebhook | boolean; - //protected abstract removeWebhook(owner: string, repo: string, id: number): Promise; - protected abstract getBranches(repo: string): Promise | undefined; - protected abstract getReferences(repo: string): Promise | undefined; - protected abstract getPullrequests(repo: string): Promise | undefined; -} \ No newline at end of file + return { keys: keys, repository: repository, webhook: webhook }; + } + + public async disconnectRepo(gitrepo: string): Promise { + this.logger.log('disconnectPipeline: ' + gitrepo); + + const { owner, repo } = this.parseRepo(gitrepo); + + // TODO: implement remove deploy key and webhook for all providers + //this.removeDeployKey(owner, repo, 0); + //this.removeWebhook(owner, repo, 0); + + return true; + } + + protected parseRepo(gitrepo: string): { owner: string; repo: string } { + const owner = gitrepo.match( + /^git@.{0,100}:(.{0,100})\/.{0,100}$/, + )?.[1] as string; + const repo = gitrepo.match( + /^git@.{0,100}:.{0,100}\/(.{0,100}).git$/, + )?.[1] as string; + return { owner: owner, repo: repo }; + } + + protected abstract addDeployKey( + owner: string, + repo: string, + ): Promise; + //protected abstract removeDeployKey(owner: string, repo: string, id: number): Promise + protected abstract getRepository(gitrepo: string): Promise; + protected abstract addWebhook( + owner: string, + repo: string, + url: string, + secret: string, + ): Promise; + protected abstract getWebhook( + event: string, + delivery: string, + signature: string, + body: any, + ): IWebhook | boolean; + //protected abstract removeWebhook(owner: string, repo: string, id: number): Promise; + protected abstract getBranches(repo: string): Promise | undefined; + protected abstract getReferences(repo: string): Promise | undefined; + protected abstract getPullrequests( + repo: string, + ): Promise | undefined; +} diff --git a/server-refactored-v3/src/repo/git/types.ts b/server-refactored-v3/src/repo/git/types.ts index 74ca2173..055ddfc0 100644 --- a/server-refactored-v3/src/repo/git/types.ts +++ b/server-refactored-v3/src/repo/git/types.ts @@ -1,80 +1,80 @@ export interface IWebhook { - repoprovider: 'gitea' | 'gitlab' | 'github' | 'bitbucket' | 'gogs' | 'onedev', - action: 'opened' | 'reopened' | 'closed' | undefined, - event: string, - delivery: string, - body: any, - branch: string, - verified: boolean, - repo: { - ssh_url: string, - } + repoprovider: 'gitea' | 'gitlab' | 'github' | 'bitbucket' | 'gogs' | 'onedev'; + action: 'opened' | 'reopened' | 'closed' | undefined; + event: string; + delivery: string; + body: any; + branch: string; + verified: boolean; + repo: { + ssh_url: string; + }; } export interface IRepository { - status: number, - statusText: 'error' | 'not found' | 'found', - data: { - id?: number | string, // bitbucket uses UUID's - node_id?: string, - name: string, - description?: string, - owner: string, - private?: boolean, - ssh_url?: string, - clone_url?: string, - language?: string, - homepage?: string, - admin: boolean, - push: boolean, - visibility?: string, - default_branch?: string - } + status: number; + statusText: 'error' | 'not found' | 'found'; + data: { + id?: number | string; // bitbucket uses UUID's + node_id?: string; + name: string; + description?: string; + owner: string; + private?: boolean; + ssh_url?: string; + clone_url?: string; + language?: string; + homepage?: string; + admin: boolean; + push: boolean; + visibility?: string; + default_branch?: string; + }; } export interface IWebhookR { - status: number, - statusText: 'error' | 'created' | 'not found' | 'found', - data: { - id?: number | string, // bitbucket uses UUID's - active: boolean, - created_at: string, - url: string, - insecure: boolean, - events: string[], - } + status: number; + statusText: 'error' | 'created' | 'not found' | 'found'; + data: { + id?: number | string; // bitbucket uses UUID's + active: boolean; + created_at: string; + url: string; + insecure: boolean; + events: string[]; + }; } export interface IDeploykeyR { - status: number, - statusText: 'error' | 'created' | 'not found' | 'found', - data: { - id?: number, - title: string, - verified: boolean, - created_at: string, - url: string, - read_only: boolean, - pub: string, - priv: string - } + status: number; + statusText: 'error' | 'created' | 'not found' | 'found'; + data: { + id?: number; + title: string; + verified: boolean; + created_at: string; + url: string; + read_only: boolean; + pub: string; + priv: string; + }; } export interface IPullrequest { - html_url: string, - number: number, - title: string, - state: string, - user: { - login: string, - avatar_url: string, - }, - created_at: string, - updated_at: string, - closed_at: string, - merged_at: string, - locked?: boolean, - draft?: boolean, - branch: string, - ssh_url: string, + html_url: string; + number: number; + title: string; + state: string; + user: { + login: string; + avatar_url: string; + }; + created_at: string; + updated_at: string; + closed_at: string; + merged_at: string; + locked?: boolean; + draft?: boolean; + branch: string; + ssh_url: string; } diff --git a/server-refactored-v3/src/repo/repo.controller.ts b/server-refactored-v3/src/repo/repo.controller.ts index 4d096ea9..d06a5b2c 100644 --- a/server-refactored-v3/src/repo/repo.controller.ts +++ b/server-refactored-v3/src/repo/repo.controller.ts @@ -4,9 +4,7 @@ import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/repo', version: '1' }) export class RepoController { - constructor( - private readonly repoService: RepoService, - ) {} + constructor(private readonly repoService: RepoService) {} @ApiOperation({ summary: 'Get a list of all available repository providers' }) @Get('/providers') @@ -16,9 +14,7 @@ export class RepoController { @ApiOperation({ summary: 'Get a list of all available repositories' }) @Get('/:provider/repositories') - async listRepositoriesByProvider( - @Param('provider') provider: string, - ) { + async listRepositoriesByProvider(@Param('provider') provider: string) { return this.repoService.listRepositoriesByProvider(provider); } @@ -51,27 +47,19 @@ export class RepoController { @Post('/:provider/connect') @ApiOperation({ summary: 'Connect a repository' }) - async connectRepo( - @Param('provider') provider: string, - @Body() body: any, - ) { + async connectRepo(@Param('provider') provider: string, @Body() body: any) { return this.repoService.connectRepo(provider, body.gitrepo); } @ApiOperation({ summary: 'Disconnect a repository' }) @Post('/:provider/disconnect') - async disconnectRepo( - @Param('provider') provider: string, - @Body() body: any, - ) { + async disconnectRepo(@Param('provider') provider: string, @Body() body: any) { return this.repoService.disconnectRepo(provider, body.gitrepo); } @ApiOperation({ summary: 'Webhooks endpoint for repository providers' }) @Post('/repo/webhooks/:provider') - async repositoryWebhook( - @Body() body: any, - ) { - return "Not implemented"; + async repositoryWebhook(@Body() body: any) { + return 'Not implemented'; } } diff --git a/server-refactored-v3/src/repo/repo.interface.ts b/server-refactored-v3/src/repo/repo.interface.ts index f21ffb0c..d75c20f5 100644 --- a/server-refactored-v3/src/repo/repo.interface.ts +++ b/server-refactored-v3/src/repo/repo.interface.ts @@ -4,4 +4,4 @@ export interface IDeployKeyPair { pubKeyBase64: string; privKey: string; privKeyBase64: string; -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/repo/repo.module.ts b/server-refactored-v3/src/repo/repo.module.ts index bcbacd8c..0cac08be 100644 --- a/server-refactored-v3/src/repo/repo.module.ts +++ b/server-refactored-v3/src/repo/repo.module.ts @@ -4,6 +4,6 @@ import { RepoService } from './repo.service'; @Module({ controllers: [RepoController], - providers: [RepoService] + providers: [RepoService], }) export class RepoModule {} diff --git a/server-refactored-v3/src/repo/repo.service.ts b/server-refactored-v3/src/repo/repo.service.ts index f6eb6fb0..ffeebec0 100644 --- a/server-refactored-v3/src/repo/repo.service.ts +++ b/server-refactored-v3/src/repo/repo.service.ts @@ -16,204 +16,227 @@ export class RepoService { private bitbucketApi: BitbucketApi; constructor() { - this.giteaApi = new GiteaApi(process.env.GITEA_BASEURL as string, process.env.GITEA_PERSONAL_ACCESS_TOKEN as string); - this.gogsApi = new GogsApi(process.env.GOGS_BASEURL as string, process.env.GOGS_PERSONAL_ACCESS_TOKEN as string); - this.githubApi = new GithubApi(process.env.GITHUB_PERSONAL_ACCESS_TOKEN as string); - this.gitlabApi = new GitlabApi(process.env.GITLAB_BASEURL as string, process.env.GITLAB_PERSONAL_ACCESS_TOKEN as string); - this.bitbucketApi = new BitbucketApi(process.env.BITBUCKET_USERNAME as string, process.env.BITBUCKET_APP_PASSWORD as string); + this.giteaApi = new GiteaApi( + process.env.GITEA_BASEURL as string, + process.env.GITEA_PERSONAL_ACCESS_TOKEN as string, + ); + this.gogsApi = new GogsApi( + process.env.GOGS_BASEURL as string, + process.env.GOGS_PERSONAL_ACCESS_TOKEN as string, + ); + this.githubApi = new GithubApi( + process.env.GITHUB_PERSONAL_ACCESS_TOKEN as string, + ); + this.gitlabApi = new GitlabApi( + process.env.GITLAB_BASEURL as string, + process.env.GITLAB_PERSONAL_ACCESS_TOKEN as string, + ); + this.bitbucketApi = new BitbucketApi( + process.env.BITBUCKET_USERNAME as string, + process.env.BITBUCKET_APP_PASSWORD as string, + ); } - public async listReferences(repoProvider: string, repoB64: string ): Promise { - //return this.git.listRepoBranches(repo, repoProvider); - let ref: Promise = new Promise((resolve, reject) => { - resolve([]); - }); - - const repo = Buffer.from(repoB64, 'base64').toString('ascii'); - - switch (repoProvider) { - case 'github': - ref = this.githubApi.getReferences(repo); - break; - case 'gitea': - ref = this.giteaApi.getReferences(repo); - break; - case 'gogs': - ref = this.gogsApi.getReferences(repo); - break; - case 'gitlab': - ref = this.gitlabApi.getReferences(repo); - break; - case 'bitbucket': - ref = this.bitbucketApi.getReferences(repo); - break; - case 'onedev': - default: - break; - } - - return ref + public async listReferences( + repoProvider: string, + repoB64: string, + ): Promise { + //return this.git.listRepoBranches(repo, repoProvider); + let ref: Promise = new Promise((resolve, reject) => { + resolve([]); + }); + + const repo = Buffer.from(repoB64, 'base64').toString('ascii'); + + switch (repoProvider) { + case 'github': + ref = this.githubApi.getReferences(repo); + break; + case 'gitea': + ref = this.giteaApi.getReferences(repo); + break; + case 'gogs': + ref = this.gogsApi.getReferences(repo); + break; + case 'gitlab': + ref = this.gitlabApi.getReferences(repo); + break; + case 'bitbucket': + ref = this.bitbucketApi.getReferences(repo); + break; + case 'onedev': + default: + break; + } + + return ref; } public async listRepositoriesByProvider(repoProvider: string) { - this.logger.debug('listRepos: '+repoProvider); - - switch (repoProvider) { - case 'github': - return this.githubApi.listRepos(); - case 'gitea': - return this.giteaApi.listRepos(); - case 'gogs': - return this.gogsApi.listRepos(); - case 'gitlab': - return this.gitlabApi.listRepos(); - case 'bitbucket': - return this.bitbucketApi.listRepos(); - case 'onedev': - default: - return {'error': 'unknown repo provider'}; - } + this.logger.debug('listRepos: ' + repoProvider); + + switch (repoProvider) { + case 'github': + return this.githubApi.listRepos(); + case 'gitea': + return this.giteaApi.listRepos(); + case 'gogs': + return this.gogsApi.listRepos(); + case 'gitlab': + return this.gitlabApi.listRepos(); + case 'bitbucket': + return this.bitbucketApi.listRepos(); + case 'onedev': + default: + return { error: 'unknown repo provider' }; + } } public async connectRepo(repoProvider: string, repoAddress: string) { - this.logger.debug('connectRepo: '+repoProvider+' '+repoAddress); + this.logger.debug('connectRepo: ' + repoProvider + ' ' + repoAddress); switch (repoProvider) { - case 'github': - return this.githubApi.connectRepo(repoAddress); - case 'gitea': - return this.giteaApi.connectRepo(repoAddress); - case 'gogs': - return this.gogsApi.connectRepo(repoAddress); - case 'gitlab': - return this.gitlabApi.connectRepo(repoAddress); - case 'bitbucket': - return this.bitbucketApi.connectRepo(repoAddress); - case 'onedev': - default: - return {'error': 'unknown repo provider'}; + case 'github': + return this.githubApi.connectRepo(repoAddress); + case 'gitea': + return this.giteaApi.connectRepo(repoAddress); + case 'gogs': + return this.gogsApi.connectRepo(repoAddress); + case 'gitlab': + return this.gitlabApi.connectRepo(repoAddress); + case 'bitbucket': + return this.bitbucketApi.connectRepo(repoAddress); + case 'onedev': + default: + return { error: 'unknown repo provider' }; } } public async disconnectRepo(repoProvider: string, repoAddress: string) { - this.logger.debug('disconnectRepo: '+repoProvider+' '+repoAddress); - - switch (repoProvider) { - case 'github': - return this.githubApi.disconnectRepo(repoAddress); - case 'gitea': - return this.giteaApi.disconnectRepo(repoAddress); - case 'gogs': - return this.gogsApi.disconnectRepo(repoAddress); - case 'gitlab': - return this.gitlabApi.disconnectRepo(repoAddress); - case 'bitbucket': - return this.bitbucketApi.disconnectRepo(repoAddress); - case 'onedev': - default: - return {'error': 'unknown repo provider'}; - } + this.logger.debug('disconnectRepo: ' + repoProvider + ' ' + repoAddress); + + switch (repoProvider) { + case 'github': + return this.githubApi.disconnectRepo(repoAddress); + case 'gitea': + return this.giteaApi.disconnectRepo(repoAddress); + case 'gogs': + return this.gogsApi.disconnectRepo(repoAddress); + case 'gitlab': + return this.gitlabApi.disconnectRepo(repoAddress); + case 'bitbucket': + return this.bitbucketApi.disconnectRepo(repoAddress); + case 'onedev': + default: + return { error: 'unknown repo provider' }; + } } - public async listBranches(repoProvider: string, repoB64: string ): Promise { - //return this.git.listRepoBranches(repo, repoProvider); - let branches: Promise = new Promise((resolve, reject) => { - resolve([]); - }); - - const repo = Buffer.from(repoB64, 'base64').toString('ascii'); - - switch (repoProvider) { - case 'github': - branches = this.githubApi.getBranches(repo); - break; - case 'gitea': - branches = this.giteaApi.getBranches(repo); - break; - case 'gogs': - branches = this.gogsApi.getBranches(repo); - break; - case 'gitlab': - branches = this.gitlabApi.getBranches(repo); - break; - case 'bitbucket': - branches = this.bitbucketApi.getBranches(repo); - break; - case 'onedev': - default: - break; - } - - return branches + public async listBranches( + repoProvider: string, + repoB64: string, + ): Promise { + //return this.git.listRepoBranches(repo, repoProvider); + let branches: Promise = new Promise((resolve, reject) => { + resolve([]); + }); + + const repo = Buffer.from(repoB64, 'base64').toString('ascii'); + + switch (repoProvider) { + case 'github': + branches = this.githubApi.getBranches(repo); + break; + case 'gitea': + branches = this.giteaApi.getBranches(repo); + break; + case 'gogs': + branches = this.gogsApi.getBranches(repo); + break; + case 'gitlab': + branches = this.gitlabApi.getBranches(repo); + break; + case 'bitbucket': + branches = this.bitbucketApi.getBranches(repo); + break; + case 'onedev': + default: + break; + } + + return branches; } - public async listPullrequests(repoProvider: string, repoB64: string ): Promise { - //return this.git.listRepoBranches(repo, repoProvider); - let pulls: Promise = new Promise((resolve, reject) => { - resolve([]); - }); - - const repo = Buffer.from(repoB64, 'base64').toString('ascii'); - - switch (repoProvider) { - case 'github': - pulls = this.githubApi.getPullrequests(repo); - break; - case 'gitea': - pulls = this.giteaApi.getPullrequests(repo); - break; - case 'gogs': - pulls = this.gogsApi.getPullrequests(repo); - break; - case 'gitlab': - pulls = this.gitlabApi.getPullrequests(repo); - break; - case 'bitbucket': - pulls = this.bitbucketApi.getPullrequests(repo); - break; - case 'onedev': - default: - break; - } - - return pulls + public async listPullrequests( + repoProvider: string, + repoB64: string, + ): Promise { + //return this.git.listRepoBranches(repo, repoProvider); + let pulls: Promise = new Promise((resolve, reject) => { + resolve([]); + }); + + const repo = Buffer.from(repoB64, 'base64').toString('ascii'); + + switch (repoProvider) { + case 'github': + pulls = this.githubApi.getPullrequests(repo); + break; + case 'gitea': + pulls = this.giteaApi.getPullrequests(repo); + break; + case 'gogs': + pulls = this.gogsApi.getPullrequests(repo); + break; + case 'gitlab': + pulls = this.gitlabApi.getPullrequests(repo); + break; + case 'bitbucket': + pulls = this.bitbucketApi.getPullrequests(repo); + break; + case 'onedev': + default: + break; + } + + return pulls; } public listRepositories() { - let repositories = { - github: false, - gitea: false, - gitlab: false, - gogs: false, - onedev: false, - bitbucket: false, - docker: true - } + const repositories = { + github: false, + gitea: false, + gitlab: false, + gogs: false, + onedev: false, + bitbucket: false, + docker: true, + }; if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) { - repositories.github = true; + repositories.github = true; } if (process.env.GITEA_PERSONAL_ACCESS_TOKEN) { - repositories.gitea = true; + repositories.gitea = true; } if (process.env.GITLAB_PERSONAL_ACCESS_TOKEN) { - repositories.gitlab = true; + repositories.gitlab = true; } if (process.env.GOGS_PERSONAL_ACCESS_TOKEN) { - repositories.gogs = true; + repositories.gogs = true; } if (process.env.ONEDEV_PERSONAL_ACCESS_TOKEN) { - repositories.onedev = true; + repositories.onedev = true; } if (process.env.BITBUCKET_USERNAME && process.env.BITBUCKET_APP_PASSWORD) { - repositories.bitbucket = true; + repositories.bitbucket = true; } return repositories; } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/security/security.controller.ts b/server-refactored-v3/src/security/security.controller.ts index 70a38c1f..17927682 100644 --- a/server-refactored-v3/src/security/security.controller.ts +++ b/server-refactored-v3/src/security/security.controller.ts @@ -4,9 +4,7 @@ import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/security', version: '1' }) export class SecurityController { - constructor( - private securityService: SecurityService, - ) {} + constructor(private securityService: SecurityService) {} @ApiOperation({ summary: 'Trigger a scan for a specific app' }) @Get(':pipeline/:phase/:app/scan') @@ -26,7 +24,11 @@ export class SecurityController { @Param('app') app: string, @Query('logdetails') logdetails: string, ) { - return this.securityService.getScanResult(pipeline, phase, app, logdetails === 'true'); + return this.securityService.getScanResult( + pipeline, + phase, + app, + logdetails === 'true', + ); } - } diff --git a/server-refactored-v3/src/security/security.service.ts b/server-refactored-v3/src/security/security.service.ts index 56ef3432..bc70ba35 100644 --- a/server-refactored-v3/src/security/security.service.ts +++ b/server-refactored-v3/src/security/security.service.ts @@ -11,113 +11,123 @@ export class SecurityService { constructor( private kubectl: KubernetesService, private pipelinesService: PipelinesService, - private appsService: AppsService + private appsService: AppsService, ) {} - public async getScanResult(pipeline: string, phase: string, appName: string, logdetails: boolean) { + public async getScanResult( + pipeline: string, + phase: string, + appName: string, + logdetails: boolean, + ) { const contextName = await this.pipelinesService.getContext(pipeline, phase); - const namespace = pipeline+'-'+phase; - - let scanResult = { - status: 'error', - message: 'unknown error', - deploymentstrategy: '', - pipeline: pipeline, - phase: phase, - app: appName, - namespace: namespace, - logsummary: {}, - logs: {}, - logPod: '' - } + const namespace = pipeline + '-' + phase; + + const scanResult = { + status: 'error', + message: 'unknown error', + deploymentstrategy: '', + pipeline: pipeline, + phase: phase, + app: appName, + namespace: namespace, + logsummary: {}, + logs: {}, + logPod: '', + }; if (!contextName) { - scanResult.status = 'error' - scanResult.message = 'no context found' - return scanResult; + scanResult.status = 'error'; + scanResult.message = 'no context found'; + return scanResult; } - const appresult = await this.appsService.getApp(pipeline, phase, appName) + const appresult = await this.appsService.getApp(pipeline, phase, appName); const app = appresult as IKubectlApp; - const logPod = await this.kubectl.getLatestPodByLabel(namespace, `vulnerabilityscan=${appName}`); + const logPod = await this.kubectl.getLatestPodByLabel( + namespace, + `vulnerabilityscan=${appName}`, + ); if (!logPod.name) { - scanResult.status = 'error' - scanResult.message = 'no vulnerability scan pod found' - return scanResult; + scanResult.status = 'error'; + scanResult.message = 'no vulnerability scan pod found'; + return scanResult; } let logs = ''; if (contextName) { - this.kubectl.setCurrentContext(contextName); - logs = await this.kubectl.getVulnerabilityScanLogs(namespace, logPod.name); + this.kubectl.setCurrentContext(contextName); + logs = await this.kubectl.getVulnerabilityScanLogs( + namespace, + logPod.name, + ); } if (!logs) { - scanResult.status = 'running' - scanResult.message = 'no vulnerability scan logs found' - return scanResult; + scanResult.status = 'running'; + scanResult.message = 'no vulnerability scan logs found'; + return scanResult; } const logsummary = this.getVulnSummary(logs); - scanResult.status = 'ok' - scanResult.message = 'vulnerability scan result' - scanResult.deploymentstrategy = app?.spec?.deploymentstrategy - scanResult.logsummary = logsummary - scanResult.logPod = logPod - + scanResult.status = 'ok'; + scanResult.message = 'vulnerability scan result'; + scanResult.deploymentstrategy = app?.spec?.deploymentstrategy; + scanResult.logsummary = logsummary; + scanResult.logPod = logPod; if (logdetails) { - scanResult.logs = logs; + scanResult.logs = logs; } return scanResult; } private getVulnSummary(logs: any) { - let summary = { - total: 0, - critical: 0, - high: 0, - medium: 0, - low: 0, - unknown: 0 - } + const summary = { + total: 0, + critical: 0, + high: 0, + medium: 0, + low: 0, + unknown: 0, + }; if (!logs || !logs.Results) { - this.logger.error(logs); - this.logger.error('no logs found or not able to parse results'); - return summary; + this.logger.error(logs); + this.logger.error('no logs found or not able to parse results'); + return summary; } logs.Results.forEach((target: any) => { - if (target.Vulnerabilities) { - target.Vulnerabilities.forEach((vuln: any) => { - summary.total++; - switch (vuln.Severity) { - case 'CRITICAL': - summary.critical++; - break; - case 'HIGH': - summary.high++; - break; - case 'MEDIUM': - summary.medium++; - break; - case 'LOW': - summary.low++; - break; - case 'UNKNOWN': - summary.unknown++; - break; - default: - summary.unknown++; - } - }); - } + if (target.Vulnerabilities) { + target.Vulnerabilities.forEach((vuln: any) => { + summary.total++; + switch (vuln.Severity) { + case 'CRITICAL': + summary.critical++; + break; + case 'HIGH': + summary.high++; + break; + case 'MEDIUM': + summary.medium++; + break; + case 'LOW': + summary.low++; + break; + case 'UNKNOWN': + summary.unknown++; + break; + default: + summary.unknown++; + } + }); + } }); return summary; @@ -125,44 +135,65 @@ export class SecurityService { public async startScan(pipeline: string, phase: string, appName: string) { const contextName = await this.pipelinesService.getContext(pipeline, phase); - const namespace = pipeline+'-'+phase; + const namespace = pipeline + '-' + phase; - - const appresult = await this.appsService.getApp(pipeline, phase, appName) + const appresult = await this.appsService.getApp(pipeline, phase, appName); const app = appresult as IKubectlApp; + if ( + app?.spec?.deploymentstrategy === 'git' && + app?.spec?.buildstrategy === 'plain' + ) { + //if (app?.spec?.deploymentstrategy === 'git') { - if (app?.spec?.deploymentstrategy === 'git' && app?.spec?.buildstrategy === 'plain') { - //if (app?.spec?.deploymentstrategy === 'git') { - - if (app?.spec.gitrepo?.clone_url) { - if (contextName) { - this.kubectl.setCurrentContext(contextName); - this.kubectl.createScanRepoJob(namespace, appName, app.spec.gitrepo.clone_url, app.spec.branch); - } - } else { - this.logger.debug('no git repo found to run scan'); - } - } else if (app?.spec?.deploymentstrategy === 'git' && app?.spec?.buildstrategy != 'plain') { + if (app?.spec.gitrepo?.clone_url) { if (contextName) { - this.kubectl.setCurrentContext(contextName); - this.kubectl.createScanImageJob(namespace, appName, app.spec.image.repository, app.spec.image.tag, true); + this.kubectl.setCurrentContext(contextName); + this.kubectl.createScanRepoJob( + namespace, + appName, + app.spec.gitrepo.clone_url, + app.spec.branch, + ); } + } else { + this.logger.debug('no git repo found to run scan'); + } + } else if ( + app?.spec?.deploymentstrategy === 'git' && + app?.spec?.buildstrategy != 'plain' + ) { + if (contextName) { + this.kubectl.setCurrentContext(contextName); + this.kubectl.createScanImageJob( + namespace, + appName, + app.spec.image.repository, + app.spec.image.tag, + true, + ); + } } else { - if (contextName) { - this.kubectl.setCurrentContext(contextName); - this.kubectl.createScanImageJob(namespace, appName, app.spec.image.repository, app.spec.image.tag, false); - } + if (contextName) { + this.kubectl.setCurrentContext(contextName); + this.kubectl.createScanImageJob( + namespace, + appName, + app.spec.image.repository, + app.spec.image.tag, + false, + ); + } } return { - status: 'ok', - message: 'scan started', - deploymentstrategy: app?.spec?.deploymentstrategy, - pipeline: pipeline, - phase: phase, - app: appName + status: 'ok', + message: 'scan started', + deploymentstrategy: app?.spec?.deploymentstrategy, + pipeline: pipeline, + phase: phase, + app: appName, }; } } diff --git a/server-refactored-v3/src/settings/buildpack/buildpack.ts b/server-refactored-v3/src/settings/buildpack/buildpack.ts index 9207663d..680102d7 100644 --- a/server-refactored-v3/src/settings/buildpack/buildpack.ts +++ b/server-refactored-v3/src/settings/buildpack/buildpack.ts @@ -1,85 +1,85 @@ import { IBuildpack, ISecurityContext } from '../settings.interface'; export class Buildpack implements IBuildpack { - public name: string; - public language: string; - public fetch: { - repository: string; - tag: string; - readOnlyAppStorage: boolean; - securityContext: ISecurityContext - }; - public build: { - repository: string; - tag: string; - readOnlyAppStorage: boolean; - securityContext: ISecurityContext - }; - public run: { - repository: string; - tag: string; - readOnlyAppStorage: boolean; - securityContext: ISecurityContext - }; - public tag: string; + public name: string; + public language: string; + public fetch: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext; + }; + public build: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext; + }; + public run: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext; + }; + public tag: string; - constructor( - bp: IBuildpack, - ) { - this.name = bp.name; - this.language = bp.language; - this.fetch = bp.fetch; - this.build = bp.build; - this.run = bp.run; - this.tag = bp.tag; + constructor(bp: IBuildpack) { + this.name = bp.name; + this.language = bp.language; + this.fetch = bp.fetch; + this.build = bp.build; + this.run = bp.run; + this.tag = bp.tag; - this.fetch.securityContext = Buildpack.SetSecurityContext(this.fetch.securityContext) - this.build.securityContext = Buildpack.SetSecurityContext(this.build.securityContext) - this.run.securityContext = Buildpack.SetSecurityContext(this.run.securityContext) + this.fetch.securityContext = Buildpack.SetSecurityContext( + this.fetch.securityContext, + ); + this.build.securityContext = Buildpack.SetSecurityContext( + this.build.securityContext, + ); + this.run.securityContext = Buildpack.SetSecurityContext( + this.run.securityContext, + ); + } + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + public static SetSecurityContext(s: any): ISecurityContext { + if (s == undefined) { + return { + runAsUser: 0, + runAsGroup: 0, + //fsGroup: 0, + allowPrivilegeEscalation: false, + readOnlyRootFilesystem: false, + runAsNonRoot: false, + capabilities: { + add: [], + drop: [], + }, + }; } - // function to set security context, required for backwards compatibility - // Added in v1.11.0 - public static SetSecurityContext(s: any) : ISecurityContext { - - if (s == undefined) { - return { - runAsUser: 0, - runAsGroup: 0, - //fsGroup: 0, - allowPrivilegeEscalation: false, - readOnlyRootFilesystem: false, - runAsNonRoot: false, - capabilities: { - add: [], - drop: [] - } - } - } - - let securityContext: ISecurityContext = { - runAsUser: s.runAsUser || 0, - runAsGroup: s.runAsGroup || 0, - //fsGroup: s.fsGroup || 0, - allowPrivilegeEscalation: s.allowPrivilegeEscalation || false, - readOnlyRootFilesystem: s.readOnlyRootFilesystem || false, - runAsNonRoot: s.runAsNonRoot || false, - capabilities: s.capabilities || { - add: [], - drop: [] - } - } + const securityContext: ISecurityContext = { + runAsUser: s.runAsUser || 0, + runAsGroup: s.runAsGroup || 0, + //fsGroup: s.fsGroup || 0, + allowPrivilegeEscalation: s.allowPrivilegeEscalation || false, + readOnlyRootFilesystem: s.readOnlyRootFilesystem || false, + runAsNonRoot: s.runAsNonRoot || false, + capabilities: s.capabilities || { + add: [], + drop: [], + }, + }; - if (securityContext.capabilities.add == undefined) { - securityContext.capabilities.add = [] - } - if (securityContext.capabilities.drop == undefined) { - securityContext.capabilities.drop = [] - } - - return securityContext + if (securityContext.capabilities.add == undefined) { + securityContext.capabilities.add = []; + } + if (securityContext.capabilities.drop == undefined) { + securityContext.capabilities.drop = []; } - -} \ No newline at end of file + return securityContext; + } +} diff --git a/server-refactored-v3/src/settings/kubero-config/kubero-config.ts b/server-refactored-v3/src/settings/kubero-config/kubero-config.ts index 7ab3a968..e62a98ba 100644 --- a/server-refactored-v3/src/settings/kubero-config/kubero-config.ts +++ b/server-refactored-v3/src/settings/kubero-config/kubero-config.ts @@ -3,48 +3,47 @@ import { Buildpack } from '../buildpack/buildpack'; import { PodSize } from '../podsize/podsize'; export class KuberoConfig { - public podSizeList: IPodSize[]; - public buildpacks: IBuildpack[]; - public clusterissuer: string; - public templates: { - enabled: boolean; - catalogs: [ - { - name: string; - description: string; - index: { - url: string; - format: string; - } - } - ] - } - public kubero: { - console: { - enabled: boolean; - } - readonly: boolean; - banner: { - message: string; - bgcolor: string; - fontcolor: string; - show: boolean; - } - } - constructor(kc: IKuberoConfig) { - - this.podSizeList = kc.podSizeList; - this.buildpacks = kc.buildpacks; - this.clusterissuer = kc.clusterissuer; - this.templates = kc.templates; - this.kubero = kc.kubero; + public podSizeList: IPodSize[]; + public buildpacks: IBuildpack[]; + public clusterissuer: string; + public templates: { + enabled: boolean; + catalogs: [ + { + name: string; + description: string; + index: { + url: string; + format: string; + }; + }, + ]; + }; + public kubero: { + console: { + enabled: boolean; + }; + readonly: boolean; + banner: { + message: string; + bgcolor: string; + fontcolor: string; + show: boolean; + }; + }; + constructor(kc: IKuberoConfig) { + this.podSizeList = kc.podSizeList; + this.buildpacks = kc.buildpacks; + this.clusterissuer = kc.clusterissuer; + this.templates = kc.templates; + this.kubero = kc.kubero; - for (let i = 0; i < this.buildpacks.length; i++) { - this.buildpacks[i] = new Buildpack(kc.buildpacks[i]); - } + for (let i = 0; i < this.buildpacks.length; i++) { + this.buildpacks[i] = new Buildpack(kc.buildpacks[i]); + } - for (let i = 0; i < this.podSizeList.length; i++) { - this.podSizeList[i] = new PodSize(kc.podSizeList[i]); - } + for (let i = 0; i < this.podSizeList.length; i++) { + this.podSizeList[i] = new PodSize(kc.podSizeList[i]); } -} \ No newline at end of file + } +} diff --git a/server-refactored-v3/src/settings/podsize/podsize.ts b/server-refactored-v3/src/settings/podsize/podsize.ts index 6b21cc34..ba6a5c72 100644 --- a/server-refactored-v3/src/settings/podsize/podsize.ts +++ b/server-refactored-v3/src/settings/podsize/podsize.ts @@ -1,32 +1,36 @@ -import { IPodSize } from "../settings.interface"; +import { IPodSize } from '../settings.interface'; export class PodSize implements IPodSize { - public name: string; - public description: string; - public default?: boolean | undefined; - public resources: { - requests?: { - memory: string; - cpu: string; - } | undefined; - limits?: { - memory: string; - cpu: string; - } | undefined; - }; - constructor(ps: IPodSize) { - this.name = ps.name; - this.description = ps.description; - this.default = ps.default; - this.resources = { - requests: { - memory: ps.resources.requests?.memory || "", - cpu: ps.resources.requests?.cpu || "" - }, - limits: { - memory: ps.resources.limits?.memory || "", - cpu: ps.resources.limits?.cpu || "" - } + public name: string; + public description: string; + public default?: boolean | undefined; + public resources: { + requests?: + | { + memory: string; + cpu: string; + } + | undefined; + limits?: + | { + memory: string; + cpu: string; } - } -} \ No newline at end of file + | undefined; + }; + constructor(ps: IPodSize) { + this.name = ps.name; + this.description = ps.description; + this.default = ps.default; + this.resources = { + requests: { + memory: ps.resources.requests?.memory || '', + cpu: ps.resources.requests?.cpu || '', + }, + limits: { + memory: ps.resources.limits?.memory || '', + cpu: ps.resources.limits?.cpu || '', + }, + }; + } +} diff --git a/server-refactored-v3/src/settings/settings.controller.ts b/server-refactored-v3/src/settings/settings.controller.ts index 89206792..cf514429 100644 --- a/server-refactored-v3/src/settings/settings.controller.ts +++ b/server-refactored-v3/src/settings/settings.controller.ts @@ -5,55 +5,53 @@ import { ApiOperation } from '@nestjs/swagger'; @Controller({ path: 'api/settings', version: '1' }) export class SettingsController { - constructor(private readonly settingsService: SettingsService) {} - - - @ApiOperation({ summary: 'Get the Kubero settings' }) - @Get('/') - async getSettings() { - return this.settingsService.getSettings(); - } - - @ApiOperation({ summary: 'Get the banner informations' }) - @Get('/banner') - async getBanner() { - return this.settingsService.getBanner(); - } - - @ApiOperation({ summary: 'Get the templates settings' }) - @Get('/templates') - async getTemplates() { - return this.settingsService.getTemplateConfig(); - } - - @ApiOperation({ summary: 'Get the registry settings' }) - @Get('/registry') - async getRegistry() { - return this.settingsService.getRegistry(); - } - - @ApiOperation({ summary: 'List runpacks' }) - @Get('/runpacks') - async getRunpacks() { - return this.settingsService.getRunpacks(); - } - - @ApiOperation({ summary: 'Get the configured cluster issuer' }) - @Get('/clusterissuer') - async getClusterIssuer() { - return this.settingsService.getClusterIssuer(); - } - - @ApiOperation({ summary: 'List buildpacks' }) - @Get('/buildpacks') - async getBuildpacks() { - return this.settingsService.getBuildpacks(); - } - - @ApiOperation({ summary: 'List available pod sizes' }) - @Get('/podsizes') - async getPodSizes() { - return this.settingsService.getPodSizes(); - } - + constructor(private readonly settingsService: SettingsService) {} + + @ApiOperation({ summary: 'Get the Kubero settings' }) + @Get('/') + async getSettings() { + return this.settingsService.getSettings(); + } + + @ApiOperation({ summary: 'Get the banner informations' }) + @Get('/banner') + async getBanner() { + return this.settingsService.getBanner(); + } + + @ApiOperation({ summary: 'Get the templates settings' }) + @Get('/templates') + async getTemplates() { + return this.settingsService.getTemplateConfig(); + } + + @ApiOperation({ summary: 'Get the registry settings' }) + @Get('/registry') + async getRegistry() { + return this.settingsService.getRegistry(); + } + + @ApiOperation({ summary: 'List runpacks' }) + @Get('/runpacks') + async getRunpacks() { + return this.settingsService.getRunpacks(); + } + + @ApiOperation({ summary: 'Get the configured cluster issuer' }) + @Get('/clusterissuer') + async getClusterIssuer() { + return this.settingsService.getClusterIssuer(); + } + + @ApiOperation({ summary: 'List buildpacks' }) + @Get('/buildpacks') + async getBuildpacks() { + return this.settingsService.getBuildpacks(); + } + + @ApiOperation({ summary: 'List available pod sizes' }) + @Get('/podsizes') + async getPodSizes() { + return this.settingsService.getPodSizes(); + } } diff --git a/server-refactored-v3/src/settings/settings.interface.ts b/server-refactored-v3/src/settings/settings.interface.ts index 117aed86..ae536ed5 100644 --- a/server-refactored-v3/src/settings/settings.interface.ts +++ b/server-refactored-v3/src/settings/settings.interface.ts @@ -1,139 +1,138 @@ import { INotificationConfig } from '../notifications/notifications.interface'; export interface IKuberoConfig { - podSizeList: IPodSize[]; - buildpacks: IBuildpack[]; - clusterissuer: string; - notifications: INotificationConfig[]; - templates: { - enabled: boolean; - catalogs: [ - { - name: string; - description: string; - index: { - url: string; - format: string; - } - } - ] - } - kubero: { - console: { - enabled: boolean; - } - admin: { - disabled: boolean; - } - readonly: boolean; - banner: { - message: string; - bgcolor: string; - fontcolor: string; - show: boolean; - } - } + podSizeList: IPodSize[]; + buildpacks: IBuildpack[]; + clusterissuer: string; + notifications: INotificationConfig[]; + templates: { + enabled: boolean; + catalogs: [ + { + name: string; + description: string; + index: { + url: string; + format: string; + }; + }, + ]; + }; + kubero: { + console: { + enabled: boolean; + }; + admin: { + disabled: boolean; + }; + readonly: boolean; + banner: { + message: string; + bgcolor: string; + fontcolor: string; + show: boolean; + }; + }; } export type IKuberoCRD = { - kubero: { - debug: string - namespace: string - context: string - webhook_url: string - auth: { - github: { - enabled: boolean - id: string - callbackUrl: string - org: string - } - oauth2: { - enabled: boolean - name: string - id: string - authUrl: string - tokenUrl: string - secret: string - callbackUrl: string - scope: string - } - } - auditLogs: { - enabled: boolean - storageClassName: any - accessModes: Array - size: string - limit: number - } - config: IKuberoConfig - } - } - + kubero: { + debug: string; + namespace: string; + context: string; + webhook_url: string; + auth: { + github: { + enabled: boolean; + id: string; + callbackUrl: string; + org: string; + }; + oauth2: { + enabled: boolean; + name: string; + id: string; + authUrl: string; + tokenUrl: string; + secret: string; + callbackUrl: string; + scope: string; + }; + }; + auditLogs: { + enabled: boolean; + storageClassName: any; + accessModes: Array; + size: string; + limit: number; + }; + config: IKuberoConfig; + }; +}; + export interface IPodSize { - name: string; - description: string, - default?: boolean, - active?: boolean, - resources: { - requests?: { - memory: string, - cpu: string - }, - limits?: { - memory: string, - cpu: string - } - } + name: string; + description: string; + default?: boolean; + active?: boolean; + resources: { + requests?: { + memory: string; + cpu: string; + }; + limits?: { + memory: string; + cpu: string; + }; + }; } export interface IBuildpack { - name: string; - language: string; - fetch: { - repository: string; - tag: string; - readOnlyAppStorage: boolean; - securityContext: ISecurityContext - }, - build: { - repository: string; - tag: string; - readOnlyAppStorage: boolean; - securityContext: ISecurityContext - }, - run: { - repository: string; - tag: string; - readOnlyAppStorage: boolean; - securityContext: ISecurityContext - }, + name: string; + language: string; + fetch: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext; + }; + build: { + repository: string; tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext; + }; + run: { + repository: string; + tag: string; + readOnlyAppStorage: boolean; + securityContext: ISecurityContext; + }; + tag: string; } export interface ISecurityContext { - readOnlyRootFilesystem: boolean; - allowPrivilegeEscalation: boolean; - runAsUser: number; - runAsGroup: number; - runAsNonRoot: boolean; - capabilities: { - drop: string[]; - add: string[]; - } + readOnlyRootFilesystem: boolean; + allowPrivilegeEscalation: boolean; + runAsUser: number; + runAsGroup: number; + runAsNonRoot: boolean; + capabilities: { + drop: string[]; + add: string[]; + }; } export type IRegistry = { - account: { - hash: string - password: string - username: string - } - create: boolean - enabled: boolean - host: string - port: number - storage: string - storageClassName: any - subpath: string -} - \ No newline at end of file + account: { + hash: string; + password: string; + username: string; + }; + create: boolean; + enabled: boolean; + host: string; + port: number; + storage: string; + storageClassName: any; + subpath: string; +}; diff --git a/server-refactored-v3/src/settings/settings.module.ts b/server-refactored-v3/src/settings/settings.module.ts index b9a21f9a..5e4b67b2 100644 --- a/server-refactored-v3/src/settings/settings.module.ts +++ b/server-refactored-v3/src/settings/settings.module.ts @@ -1,7 +1,7 @@ import { Global, Module } from '@nestjs/common'; import { SettingsController } from './settings.controller'; import { SettingsService } from './settings.service'; -import { KubernetesModule } from '../kubernetes/kubernetes.module'; +import { KubernetesModule } from '../kubernetes/kubernetes.module'; @Global() @Module({ diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 840d6d7b..84df1a84 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -3,7 +3,7 @@ import { IKuberoCRD, IKuberoConfig, IRegistry } from './settings.interface'; import { KuberoConfig } from './kubero-config/kubero-config'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { readFileSync, writeFileSync } from 'fs'; -import YAML from 'yaml' +import YAML from 'yaml'; import { join } from 'path'; import { Context } from '@kubernetes/client-node'; import { Buildpack } from './buildpack/buildpack'; @@ -11,12 +11,12 @@ import { PodSize } from './podsize/podsize'; @Injectable() export class SettingsService { - private readonly logger = new Logger(SettingsService.name); - private runningConfig: IKuberoConfig - private features: {[key: string]: boolean} = { - sleep: false, - metrics: false, - /* suggested features + private readonly logger = new Logger(SettingsService.name); + private runningConfig: IKuberoConfig; + private features: { [key: string]: boolean } = { + sleep: false, + metrics: false, + /* suggested features console: false, logs: false, audit: false, @@ -27,317 +27,331 @@ export class SettingsService { security: false, settings: false, */ - } + }; - constructor( - private readonly kubectl: KubernetesService, - ) { - this.reloadRunningConfig() - this.runFeatureCheck() - } + constructor(private readonly kubectl: KubernetesService) { + this.reloadRunningConfig(); + this.runFeatureCheck(); + } - // Load settings from a file or from kubernetes - async getSettings(): Promise { + // Load settings from a file or from kubernetes + async getSettings(): Promise { + if (this.checkAdminDisabled()) { + return new KuberoConfig(new Object() as IKuberoConfig); + } - if (this.checkAdminDisabled()) { - return new KuberoConfig(new Object() as IKuberoConfig) - } - - // TODO: This might fail with a local filesystem config - let config: any = {} - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - let kuberoes = await this.kubectl.getKuberoConfig(namespace) - config.settings = kuberoes.spec - /* + // TODO: This might fail with a local filesystem config + const config: any = {}; + const namespace = process.env.KUBERO_NAMESPACE || 'kubero'; + const kuberoes = await this.kubectl.getKuberoConfig(namespace); + config.settings = kuberoes.spec; + /* const kuberoconfig = await this.readConfig() config.settings = new KuberoConfig(kuberoconfig) */ - - config["secrets"] = { - GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PERSONAL_ACCESS_TOKEN || '', - GITEA_PERSONAL_ACCESS_TOKEN: process.env.GITEA_PERSONAL_ACCESS_TOKEN || '', - GITEA_BASEURL: process.env.GITEA_BASEURL || '', - GITLAB_PERSONAL_ACCESS_TOKEN: process.env.GITLAB_PERSONAL_ACCESS_TOKEN || '', - GITLAB_BASEURL: process.env.GITLAB_BASEURL || '', - BITBUCKET_APP_PASSWORD: process.env.BITBUCKET_APP_PASSWORD || '', - BITBUCKET_USERNAME: process.env.BITBUCKET_USERNAME || '', - GOGS_PERSONAL_ACCESS_TOKEN: process.env.GOGS_PERSONAL_ACCESS_TOKEN || '', - GOGS_BASEURL: process.env.GOGS_BASEURL || '', - KUBERO_WEBHOOK_SECRET: process.env.KUBERO_WEBHOOK_SECRET || '', - GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET || '', - OAUTH2_CLIENT_SECRET: process.env.OAUTH2_CLIENT_SECRET || '', - } - return config + config['secrets'] = { + GITHUB_PERSONAL_ACCESS_TOKEN: + process.env.GITHUB_PERSONAL_ACCESS_TOKEN || '', + GITEA_PERSONAL_ACCESS_TOKEN: + process.env.GITEA_PERSONAL_ACCESS_TOKEN || '', + GITEA_BASEURL: process.env.GITEA_BASEURL || '', + GITLAB_PERSONAL_ACCESS_TOKEN: + process.env.GITLAB_PERSONAL_ACCESS_TOKEN || '', + GITLAB_BASEURL: process.env.GITLAB_BASEURL || '', + BITBUCKET_APP_PASSWORD: process.env.BITBUCKET_APP_PASSWORD || '', + BITBUCKET_USERNAME: process.env.BITBUCKET_USERNAME || '', + GOGS_PERSONAL_ACCESS_TOKEN: process.env.GOGS_PERSONAL_ACCESS_TOKEN || '', + GOGS_BASEURL: process.env.GOGS_BASEURL || '', + KUBERO_WEBHOOK_SECRET: process.env.KUBERO_WEBHOOK_SECRET || '', + GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET || '', + OAUTH2_CLIENT_SECRET: process.env.OAUTH2_CLIENT_SECRET || '', + }; + return config; + } + + private reloadRunningConfig(): void { + this.readConfig() + .then((config) => { + this.logger.debug('Kubero config loaded'); + this.runningConfig = config; + }) + .catch((error) => { + this.logger.error('Error reading kuberoes config'); + this.logger.error(error); + }); + } + + private async readConfig(): Promise { + if (process.env.NODE_ENV === 'production') { + const kuberoCRD = await this.readConfigFromKubernetes(); + return kuberoCRD.kubero.config; + } else { + return this.readConfigFromFS(); } - - private reloadRunningConfig(): void { - this.readConfig().then((config) => { - this.logger.debug('Kubero config loaded') - this.runningConfig = config - }).catch((error) => { - this.logger.error('Error reading kuberoes config') - this.logger.error(error) - }) + } + + private async readConfigFromKubernetes(): Promise { + const namespace = process.env.KUBERO_NAMESPACE || 'kubero'; + const kuberoes = await this.kubectl.getKuberoConfig(namespace); + return kuberoes.spec; + } + + private readConfigFromFS(): IKuberoConfig { + // read config from local filesystem (dev mode) + //const path = join(__dirname, 'config.yaml') + const path = + process.env.KUBERO_CONFIG_PATH || join(__dirname, 'config.yaml'); + let settings: string; + try { + settings = readFileSync(path, 'utf8'); + return YAML.parse(settings) as IKuberoConfig; + } catch (e) { + this.logger.error('Error reading config file'); + + return new Object() as IKuberoConfig; } - - private async readConfig(): Promise { - if (process.env.NODE_ENV === "production") { - const kuberoCRD = await this.readConfigFromKubernetes() - return kuberoCRD.kubero.config - } else { - return this.readConfigFromFS() - } - } - - - private async readConfigFromKubernetes(): Promise { - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - let kuberoes = await this.kubectl.getKuberoConfig(namespace) - return kuberoes.spec - } - - private readConfigFromFS(): IKuberoConfig { - // read config from local filesystem (dev mode) - //const path = join(__dirname, 'config.yaml') - const path = process.env.KUBERO_CONFIG_PATH || join(__dirname, 'config.yaml') - let settings: string - try { - settings = readFileSync( path, 'utf8') - return YAML.parse(settings) as IKuberoConfig - } catch (e) { - this.logger.error('Error reading config file') - - return new Object() as IKuberoConfig - } + } + + // write config to local filesystem (dev mode) + private writeConfig(configMap: KuberoConfig) { + const path = + process.env.KUBERO_CONFIG_PATH || join(__dirname, 'config.yaml'); + writeFileSync(path, YAML.stringify(configMap), { + flag: 'w', + encoding: 'utf8', + }); + } + + public async getDefaultRegistry(): Promise { + 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) { + this.logger.error('Error getting kuberoes config'); } - - // write config to local filesystem (dev mode) - private writeConfig(configMap: KuberoConfig) { - const path = process.env.KUBERO_CONFIG_PATH || join(__dirname, 'config.yaml') - writeFileSync(path, YAML.stringify(configMap), { - flag: 'w', - encoding: 'utf8' - }); + return registry; + } + + public async getBanner(): Promise { + const defaultbanner = { + show: false, + text: '', + bgcolor: 'white', + fontcolor: 'white', + }; + + const banner = (await this.runningConfig.kubero?.banner) || defaultbanner; + return banner; + } + + public checkAdminDisabled(): boolean { + 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', + }; } - - public async getDefaultRegistry(): Promise { - - 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) { - this.logger.error("Error getting kuberoes config") - } - return registry + 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', + }; } - public async getBanner(): Promise { - let defaultbanner = { - show: false, - text: "", - bgcolor: "white", - fontcolor: "white" - } - - let banner = await this.runningConfig.kubero?.banner || defaultbanner; - return banner + 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 { + const ret = { + //reason : "Component not found", + status: 'error', + }; + + if (component === 'operator') { + //let operator = await this.kubectl.checkCustomResourceDefinition("kuberoes.application.kubero.dev") + const operator = await this.kubectl.checkNamespace( + 'kubero-operator-system', + ); + if (operator) { + ret.status = 'ok'; + } } - public checkAdminDisabled(): boolean { - return this.runningConfig.kubero.admin?.disabled || false + if (component === 'metrics') { + const metrics = await this.kubectl.checkDeployment( + 'kube-system', + 'metrics-server', + ); + if (metrics) { + ret.status = 'ok'; + } } - 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) + if (component === 'debug') { + const metrics = await this.kubectl.checkNamespace('default'); + if (metrics) { + ret.status = 'ok'; + } } - 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" - } + if (component === 'ingress') { + const ingress = await this.kubectl.checkNamespace('ingress-nginx'); + if (ingress) { + ret.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" - } - } + return ret; + } - if (component === "metrics") { - let metrics = await this.kubectl.checkDeployment("kube-system", "metrics-server") - if (metrics) { - ret.status = "ok" - } - } + getBuildpipelineEnabled() { + return process.env.KUBERO_BUILD_REGISTRY + ? process.env.KUBERO_BUILD_REGISTRY != undefined + : false; + } - if (component === "debug") { - let metrics = await this.kubectl.checkNamespace("default") - if (metrics) { - ret.status = "ok" - } - } + getTemplateEnabled() { + return this.runningConfig.templates?.enabled || false; + } - if (component === "ingress") { - let ingress = await this.kubectl.checkNamespace("ingress-nginx") - if (ingress) { - ret.status = "ok" - } - } + public async getTemplateConfig() { + return this.runningConfig.templates; + } - return ret + getConsoleEnabled() { + if (this.runningConfig.kubero?.console?.enabled == undefined) { + return false; } - - getBuildpipelineEnabled(){ - return process.env.KUBERO_BUILD_REGISTRY ? process.env.KUBERO_BUILD_REGISTRY != undefined : false - } - - getTemplateEnabled(){ - return this.runningConfig.templates?.enabled || false - } - - public async getTemplateConfig() { - return this.runningConfig.templates - } - - getConsoleEnabled(){ - if (this.runningConfig.kubero?.console?.enabled == undefined) { - return false; + return this.runningConfig.kubero?.console?.enabled; + } + + setMetricsStatus(status: boolean) { + this.features.metrics = status; + } + + getMetricsEnabled(): boolean { + return this.features.metrics; + } + + private async checkForZeropod(): Promise { + // This is a very basic check for Zeropod. It requires the namespace zeropod-system to be present. + // But it does not check if the Zeropod controller is complete and running. + let enabled = false; + try { + const nsList = await this.kubectl.getNamespaces(); + for (const ns of nsList) { + if (ns.metadata?.name == 'zeropod-system') { + enabled = true; } - return this.runningConfig.kubero?.console?.enabled; - } - - setMetricsStatus(status: boolean) { - this.features.metrics = status + } + } catch (error) { + this.logger.error('❌ getSleepEnabled: could not check for Zeropod'); + return false; } - getMetricsEnabled(): boolean{ - return this.features.metrics - } + return enabled; + } - private async checkForZeropod(): Promise { - // This is a very basic check for Zeropod. It requires the namespace zeropod-system to be present. - // But it does not check if the Zeropod controller is complete and running. - let enabled = false - try { - const nsList = await this.kubectl.getNamespaces() - for (const ns of nsList) { - if (ns.metadata?.name == 'zeropod-system') { - enabled = true - } - } - } catch (error) { - this.logger.error('❌ getSleepEnabled: could not check for Zeropod') - return false - } - - return enabled - } + private async runFeatureCheck() { + this.features.sleep = await this.checkForZeropod(); + } - private async runFeatureCheck() { - this.features.sleep = await this.checkForZeropod() - } + public getSleepEnabled(): boolean { + return this.features.sleep; + } - public getSleepEnabled(): boolean { - return this.features.sleep - } + public async getRegistry(): Promise { + const namespace = process.env.KUBERO_NAMESPACE || 'kubero'; + const kuberoes = await this.kubectl.getKuberoConfig(namespace); + return kuberoes.spec.registry; + } - public async getRegistry(): Promise { - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - let kuberoes = await this.kubectl.getKuberoConfig(namespace) - return kuberoes.spec.registry - } + public getRunpacks(): any[] { + return this.runningConfig.buildpacks || []; + } - public getRunpacks(): any[] { - return this.runningConfig.buildpacks || [] + public async getClusterIssuer(): Promise<{ clusterissuer: string }> { + const namespace = process.env.KUBERO_NAMESPACE || 'kubero'; + const kuberoes = await this.kubectl.getKuberoConfig(namespace); + if (kuberoes == undefined) { + return { clusterissuer: 'not-configured' }; } + return { + clusterissuer: + kuberoes.spec.kubero.config.clusterissuer || 'not-configured', + }; + } - public async getClusterIssuer(): Promise<{clusterissuer: string}> { - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - let kuberoes = await this.kubectl.getKuberoConfig(namespace) - if (kuberoes == undefined) { - return { clusterissuer: "not-configured" } - } - return { - clusterissuer: kuberoes.spec.kubero.config.clusterissuer || "not-configured" - } - } - - public async getBuildpacks() { - let buildpackList: Buildpack[] = []; + public async getBuildpacks() { + const buildpackList: Buildpack[] = []; - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - let kuberoes = await this.kubectl.getKuberoConfig(namespace) + const namespace = process.env.KUBERO_NAMESPACE || 'kubero'; + const kuberoes = await this.kubectl.getKuberoConfig(namespace); - for (const buildpack of kuberoes.spec.kubero.config.buildpacks) { - const b = new Buildpack(buildpack); - buildpackList.push(b); - } - - return buildpackList; + for (const buildpack of kuberoes.spec.kubero.config.buildpacks) { + const b = new Buildpack(buildpack); + buildpackList.push(b); } - public async getPodSizes() { - let podSizeList: PodSize[] = []; + return buildpackList; + } - const namespace = process.env.KUBERO_NAMESPACE || "kubero" - let kuberoes = await this.kubectl.getKuberoConfig(namespace) + public async getPodSizes() { + const podSizeList: PodSize[] = []; - for (const podSize of kuberoes.spec.kubero.config.podSizeList) { - const p = new PodSize(podSize); - podSizeList.push(p); - } + const namespace = process.env.KUBERO_NAMESPACE || 'kubero'; + const kuberoes = await this.kubectl.getKuberoConfig(namespace); - return podSizeList; + for (const podSize of kuberoes.spec.kubero.config.podSizeList) { + const p = new PodSize(podSize); + podSizeList.push(p); } + return podSizeList; + } } diff --git a/server-refactored-v3/src/templates/template.ts b/server-refactored-v3/src/templates/template.ts index a78fd33a..833aee87 100644 --- a/server-refactored-v3/src/templates/template.ts +++ b/server-refactored-v3/src/templates/template.ts @@ -3,10 +3,10 @@ import { IApp, IExtraVolume, ICronjob } from '../apps/apps.interface'; import { IAddon } from '../addons/addons.interface'; import { IKubectlMetadata } from '../kubernetes/kubernetes.interface'; -export class Template implements ITemplate{ - public name: string - public deploymentstrategy: 'git' | 'docker' - public envVars: {}[] = [] +export class Template implements ITemplate { + public name: string; + public deploymentstrategy: 'git' | 'docker'; + public envVars: {}[] = []; /* public serviceAccount: { annotations: Object @@ -14,24 +14,24 @@ export class Template implements ITemplate{ name: string, }; */ - public extraVolumes: IExtraVolume[] = [] - public cronjobs: ICronjob[] = [] - public addons: IAddon[] = [] + public extraVolumes: IExtraVolume[] = []; + public cronjobs: ICronjob[] = []; + public addons: IAddon[] = []; public web: { - replicaCount: number - } + replicaCount: number; + }; public worker: { - replicaCount: number - } + replicaCount: number; + }; public image: { - containerPort: number, - pullPolicy?: 'Always', - repository: string, - tag: string, - /* + containerPort: number; + pullPolicy?: 'Always'; + repository: string; + tag: string; + /* run: { repository: string, tag: string, @@ -40,72 +40,68 @@ export class Template implements ITemplate{ } */ }; - constructor( - app: IApp - ) { - this.name = app.name - this.deploymentstrategy = app.deploymentstrategy + constructor(app: IApp) { + this.name = app.name; + this.deploymentstrategy = app.deploymentstrategy; - this.envVars = app.envVars + this.envVars = app.envVars; - //this.serviceAccount = app.serviceAccount; - - this.extraVolumes = app.extraVolumes + //this.serviceAccount = app.serviceAccount; - this.cronjobs = app.cronjobs + this.extraVolumes = app.extraVolumes; - this.addons = app.addons + this.cronjobs = app.cronjobs; - this.web = { - replicaCount: app.web.replicaCount - } - this.worker = { - replicaCount: app.worker.replicaCount - } + this.addons = app.addons; - this.image = { - containerPort: app.image.containerPort, - pullPolicy: 'Always', - repository: app.image.repository || 'ghcr.io/kubero-dev/idler', - tag: app.image.tag || 'v1', - //run: app.image.run, - } + this.web = { + replicaCount: app.web.replicaCount, + }; + this.worker = { + replicaCount: app.worker.replicaCount, + }; + + this.image = { + containerPort: app.image.containerPort, + pullPolicy: 'Always', + repository: app.image.repository || 'ghcr.io/kubero-dev/idler', + tag: app.image.tag || 'v1', + //run: app.image.run, + }; - // function to set security context, required for backwards compatibility - // Added in v1.11.0 - //this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + //this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) } } -export class KubectlTemplate implements IKubectlTemplate{ - apiVersion: string; - kind: string; - metadata: IKubectlMetadata; - spec: Template; - - constructor(app: IApp) { - this.apiVersion = "application.kubero.dev/v1alpha1"; - this.kind = "KuberoApp"; - this.metadata = { - name: app.name, - annotations: { - 'kubero.dev/template.architecture': '[]', - 'kubero.dev/template.description': '', - 'kubero.dev/template.icon': '', - 'kubero.dev/template.installation': '', - 'kubero.dev/template.links': '[]', - 'kubero.dev/template.screenshots': '[]', - 'kubero.dev/template.source': '', - 'kubero.dev/template.categories': '[]', - 'kubero.dev/template.title': '', - 'kubero.dev/template.website': '' - }, - labels: { - manager: 'kubero', - } - } - this.spec = new Template(app); - } +export class KubectlTemplate implements IKubectlTemplate { + apiVersion: string; + kind: string; + metadata: IKubectlMetadata; + spec: Template; + + constructor(app: IApp) { + this.apiVersion = 'application.kubero.dev/v1alpha1'; + this.kind = 'KuberoApp'; + this.metadata = { + name: app.name, + annotations: { + 'kubero.dev/template.architecture': '[]', + 'kubero.dev/template.description': '', + 'kubero.dev/template.icon': '', + 'kubero.dev/template.installation': '', + 'kubero.dev/template.links': '[]', + 'kubero.dev/template.screenshots': '[]', + 'kubero.dev/template.source': '', + 'kubero.dev/template.categories': '[]', + 'kubero.dev/template.title': '', + 'kubero.dev/template.website': '', + }, + labels: { + manager: 'kubero', + }, + }; + this.spec = new Template(app); + } } - - \ No newline at end of file diff --git a/server-refactored-v3/src/templates/templates.interface.ts b/server-refactored-v3/src/templates/templates.interface.ts index 6e44baf7..99bd767e 100644 --- a/server-refactored-v3/src/templates/templates.interface.ts +++ b/server-refactored-v3/src/templates/templates.interface.ts @@ -1,48 +1,47 @@ import { IExtraVolume, ICronjob } from '../apps/apps.interface'; import { IKubectlMetadata } from '../kubernetes/kubernetes.interface'; -import { ISecurityContext } from "../settings/settings.interface" +import { ISecurityContext } from '../settings/settings.interface'; import { IAddon } from '../addons/addons.interface'; export interface ITemplate { - name: string, - deploymentstrategy: 'git' | 'docker', - envVars: {}[], + name: string; + deploymentstrategy: 'git' | 'docker'; + envVars: {}[]; serviceAccount?: { - annotations: {}, - create: boolean, - name: string, - }, - image : { - repository: string, - tag: string, - pullPolicy?: 'Always', - containerPort: number, - run?: { - repository: string, - readOnlyAppStorage?: boolean, - tag: string, - readOnly?: boolean, - securityContext: ISecurityContext - } - } + annotations: {}; + create: boolean; + name: string; + }; + image: { + repository: string; + tag: string; + pullPolicy?: 'Always'; + containerPort: number; + run?: { + repository: string; + readOnlyAppStorage?: boolean; + tag: string; + readOnly?: boolean; + securityContext: ISecurityContext; + }; + }; web: { - replicaCount: number - } + replicaCount: number; + }; worker: { - replicaCount: number - } + replicaCount: number; + }; - extraVolumes: IExtraVolume[], - cronjobs: ICronjob[] - addons: IAddon[] + extraVolumes: IExtraVolume[]; + cronjobs: ICronjob[]; + addons: IAddon[]; } -export interface IKubectlTemplate -{ +export interface IKubectlTemplate { apiVersion: string; kind: string; - metadata: IKubectlMetadata + metadata: IKubectlMetadata; spec: ITemplate; -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/templates/templates/templates.ts b/server-refactored-v3/src/templates/templates/templates.ts index 0bc26816..28e1d4af 100644 --- a/server-refactored-v3/src/templates/templates/templates.ts +++ b/server-refactored-v3/src/templates/templates/templates.ts @@ -1,43 +1,43 @@ -import { IApp, ICronjob, IExtraVolume } from "../../apps/apps.interface"; -import { ITemplate, IKubectlTemplate } from "../templates.interface"; -import { IKubectlMetadata } from "../../kubernetes/kubernetes.interface" -import { IAddon } from "src/addons/addons.interface"; +import { IApp, ICronjob, IExtraVolume } from '../../apps/apps.interface'; +import { ITemplate, IKubectlTemplate } from '../templates.interface'; +import { IKubectlMetadata } from '../../kubernetes/kubernetes.interface'; +import { IAddon } from 'src/addons/addons.interface'; -export class KubectlTemplate implements IKubectlTemplate{ +export class KubectlTemplate implements IKubectlTemplate { apiVersion: string; kind: string; metadata: IKubectlMetadata; spec: Template; constructor(app: IApp) { - this.apiVersion = "application.kubero.dev/v1alpha1"; - this.kind = "KuberoApp"; - this.metadata = { - name: app.name, - annotations: { - 'kubero.dev/template.architecture': '[]', - 'kubero.dev/template.description': '', - 'kubero.dev/template.icon': '', - 'kubero.dev/template.installation': '', - 'kubero.dev/template.links': '[]', - 'kubero.dev/template.screenshots': '[]', - 'kubero.dev/template.source': '', - 'kubero.dev/template.categories': '[]', - 'kubero.dev/template.title': '', - 'kubero.dev/template.website': '' - }, - labels: { - manager: 'kubero', - } - } - this.spec = new Template(app); + this.apiVersion = 'application.kubero.dev/v1alpha1'; + this.kind = 'KuberoApp'; + this.metadata = { + name: app.name, + annotations: { + 'kubero.dev/template.architecture': '[]', + 'kubero.dev/template.description': '', + 'kubero.dev/template.icon': '', + 'kubero.dev/template.installation': '', + 'kubero.dev/template.links': '[]', + 'kubero.dev/template.screenshots': '[]', + 'kubero.dev/template.source': '', + 'kubero.dev/template.categories': '[]', + 'kubero.dev/template.title': '', + 'kubero.dev/template.website': '', + }, + labels: { + manager: 'kubero', + }, + }; + this.spec = new Template(app); } } -class Template implements ITemplate{ - public name: string - public deploymentstrategy: 'git' | 'docker' - public envVars: {}[] = [] +class Template implements ITemplate { + public name: string; + public deploymentstrategy: 'git' | 'docker'; + public envVars: {}[] = []; /* public serviceAccount: { annotations: Object @@ -45,24 +45,24 @@ class Template implements ITemplate{ name: string, }; */ - public extraVolumes: IExtraVolume[] = [] - public cronjobs: ICronjob[] = [] - public addons: IAddon[] = [] + public extraVolumes: IExtraVolume[] = []; + public cronjobs: ICronjob[] = []; + public addons: IAddon[] = []; public web: { - replicaCount: number - } + replicaCount: number; + }; public worker: { - replicaCount: number - } + replicaCount: number; + }; public image: { - containerPort: number, - pullPolicy?: 'Always', - repository: string, - tag: string, - /* + containerPort: number; + pullPolicy?: 'Always'; + repository: string; + tag: string; + /* run: { repository: string, tag: string, @@ -71,39 +71,37 @@ class Template implements ITemplate{ } */ }; - constructor( - app: IApp - ) { - this.name = app.name - this.deploymentstrategy = app.deploymentstrategy + constructor(app: IApp) { + this.name = app.name; + this.deploymentstrategy = app.deploymentstrategy; - this.envVars = app.envVars + this.envVars = app.envVars; - //this.serviceAccount = app.serviceAccount; - - this.extraVolumes = app.extraVolumes + //this.serviceAccount = app.serviceAccount; - this.cronjobs = app.cronjobs + this.extraVolumes = app.extraVolumes; - this.addons = app.addons + this.cronjobs = app.cronjobs; - this.web = { - replicaCount: app.web.replicaCount - } - this.worker = { - replicaCount: app.worker.replicaCount - } + this.addons = app.addons; - this.image = { - containerPort: app.image.containerPort, - pullPolicy: 'Always', - repository: app.image.repository || 'ghcr.io/kubero-dev/idler', - tag: app.image.tag || 'v1', - //run: app.image.run, - } + this.web = { + replicaCount: app.web.replicaCount, + }; + this.worker = { + replicaCount: app.worker.replicaCount, + }; - // function to set security context, required for backwards compatibility - // Added in v1.11.0 - //this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) + this.image = { + containerPort: app.image.containerPort, + pullPolicy: 'Always', + repository: app.image.repository || 'ghcr.io/kubero-dev/idler', + tag: app.image.tag || 'v1', + //run: app.image.run, + }; + + // function to set security context, required for backwards compatibility + // Added in v1.11.0 + //this.image.run.securityContext = Buildpack.SetSecurityContext(this.image.run.securityContext) } -} \ No newline at end of file +} diff --git a/server-refactored-v3/src/users/users.module.ts b/server-refactored-v3/src/users/users.module.ts index 416030ce..8fa904f1 100644 --- a/server-refactored-v3/src/users/users.module.ts +++ b/server-refactored-v3/src/users/users.module.ts @@ -5,4 +5,4 @@ import { UsersService } from './users.service'; providers: [UsersService], exports: [UsersService], }) -export class UsersModule {} \ No newline at end of file +export class UsersModule {} diff --git a/server-refactored-v3/src/users/users.service.ts b/server-refactored-v3/src/users/users.service.ts index a5b34cc7..c4f7173f 100644 --- a/server-refactored-v3/src/users/users.service.ts +++ b/server-refactored-v3/src/users/users.service.ts @@ -14,6 +14,6 @@ export class UsersService { ]; async findOne(username: string): Promise { - return this.users.find(user => user.username === username); + return this.users.find((user) => user.username === username); } -} \ No newline at end of file +} From f3e972861428a475bcef3dd31615594c1114ca44 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 13 Feb 2025 21:36:16 +0100 Subject: [PATCH 42/65] fix logs socket --- .../src/events/events.gateway.ts | 39 +++++++++++++++---- server-refactored-v3/src/logs/logs.service.ts | 8 ++-- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/server-refactored-v3/src/events/events.gateway.ts b/server-refactored-v3/src/events/events.gateway.ts index e39d00ca..84573490 100644 --- a/server-refactored-v3/src/events/events.gateway.ts +++ b/server-refactored-v3/src/events/events.gateway.ts @@ -1,4 +1,6 @@ +import { Logger } from '@nestjs/common'; import { + ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, @@ -7,7 +9,7 @@ import { } from '@nestjs/websockets'; import { from, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { Server } from 'socket.io'; +import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { @@ -18,12 +20,35 @@ export class EventsGateway { @WebSocketServer() server: Server; - // TODO: example implementation of a WebSocket event - @SubscribeMessage('events') - findAll(@MessageBody() data: any): Observable> { - return from([1, 2, 3]).pipe( - map((item) => ({ event: 'events', data: item })), - ); + constructor() { + Logger.debug('EventsGateway created'); + } + + @SubscribeMessage('join') + handleJoin( + @MessageBody() data: { room: string }, + @ConnectedSocket() client: Socket, + ): void { + Logger.debug('joining room ' + data.room); + client.join(data.room); + } + + @SubscribeMessage('leave') + handleLeave( + @MessageBody() data: { room: string }, + @ConnectedSocket() client: Socket, + ): void { + Logger.debug('leaving room ' + data.room); + client.leave(data.room); + } + + @SubscribeMessage('log') + handleEvent( + @MessageBody() data: string, + @ConnectedSocket() client: Socket, + ): string { + Logger.debug('received event ' + data); + return data; } sendEvent(event: string, data: any) { diff --git a/server-refactored-v3/src/logs/logs.service.ts b/server-refactored-v3/src/logs/logs.service.ts index 2fdd85e5..3c3c7efc 100644 --- a/server-refactored-v3/src/logs/logs.service.ts +++ b/server-refactored-v3/src/logs/logs.service.ts @@ -113,10 +113,10 @@ export class LogsService { ); } /* TODO needs some improvements since it wont load web anymore - for (const initcontainer of pod.spec.initContainers) { - this.emitLogs(pipelineName, phaseName, appName, pod.metadata.name, initcontainer.name); - } - */ + for (const initcontainer of pod.spec.initContainers) { + this.emitLogs(pipelineName, phaseName, appName, pod.metadata.name, initcontainer.name); + } + */ } } }); From 8ee30407613cf70e98dde1eee9c7c677bf693212 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 13 Feb 2025 22:32:55 +0100 Subject: [PATCH 43/65] improve API docs --- server-refactored-v3/src/app.module.ts | 6 ++-- .../src/events/events.gateway.ts | 15 ++-------- .../src/repo/repo.controller.ts | 14 ++++++++-- .../templates/templates.controller.spec.ts | 18 ++++++++++++ .../src/templates/templates.controller.ts | 28 +++++++++++++++++++ .../src/templates/templates.service.spec.ts | 18 ++++++++++++ .../src/templates/templates.service.ts | 25 +++++++++++++++++ 7 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 server-refactored-v3/src/templates/templates.controller.spec.ts create mode 100644 server-refactored-v3/src/templates/templates.controller.ts create mode 100644 server-refactored-v3/src/templates/templates.service.spec.ts create mode 100644 server-refactored-v3/src/templates/templates.service.ts diff --git a/server-refactored-v3/src/app.module.ts b/server-refactored-v3/src/app.module.ts index 2405cd9b..a45d09e0 100644 --- a/server-refactored-v3/src/app.module.ts +++ b/server-refactored-v3/src/app.module.ts @@ -18,6 +18,8 @@ import { AuditModule } from './audit/audit.module'; import { AddonsModule } from './addons/addons.module'; import { NotificationsModule } from './notifications/notifications.module'; import { SecurityModule } from './security/security.module'; +import { TemplatesController } from './templates/templates.controller'; +import { TemplatesService } from './templates/templates.service'; @Module({ imports: [ @@ -40,7 +42,7 @@ import { SecurityModule } from './security/security.module'; NotificationsModule, SecurityModule, ], - controllers: [AppController], - providers: [AppService], + controllers: [AppController, TemplatesController], + providers: [AppService, TemplatesService], }) export class AppModule {} diff --git a/server-refactored-v3/src/events/events.gateway.ts b/server-refactored-v3/src/events/events.gateway.ts index 84573490..4426ed83 100644 --- a/server-refactored-v3/src/events/events.gateway.ts +++ b/server-refactored-v3/src/events/events.gateway.ts @@ -21,7 +21,7 @@ export class EventsGateway { server: Server; constructor() { - Logger.debug('EventsGateway created'); + //Logger.debug('EventsGateway created'); } @SubscribeMessage('join') @@ -29,7 +29,7 @@ export class EventsGateway { @MessageBody() data: { room: string }, @ConnectedSocket() client: Socket, ): void { - Logger.debug('joining room ' + data.room); + //Logger.debug('joining room ' + data.room); client.join(data.room); } @@ -38,19 +38,10 @@ export class EventsGateway { @MessageBody() data: { room: string }, @ConnectedSocket() client: Socket, ): void { - Logger.debug('leaving room ' + data.room); + //Logger.debug('leaving room ' + data.room); client.leave(data.room); } - @SubscribeMessage('log') - handleEvent( - @MessageBody() data: string, - @ConnectedSocket() client: Socket, - ): string { - Logger.debug('received event ' + data); - return data; - } - sendEvent(event: string, data: any) { this.server.emit(event, data); } diff --git a/server-refactored-v3/src/repo/repo.controller.ts b/server-refactored-v3/src/repo/repo.controller.ts index d06a5b2c..f7493d43 100644 --- a/server-refactored-v3/src/repo/repo.controller.ts +++ b/server-refactored-v3/src/repo/repo.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { RepoService } from './repo.service'; -import { ApiOperation } from '@nestjs/swagger'; +import { ApiOperation, ApiParam } from '@nestjs/swagger'; @Controller({ path: 'api/repo', version: '1' }) export class RepoController { @@ -13,6 +13,7 @@ export class RepoController { } @ApiOperation({ summary: 'Get a list of all available repositories' }) + @ApiParam({ name: "provider", type: "string", description: "A git provider", required: true, enum: ['github', 'gitlab', 'bigbucket', 'gitea', 'gogs'] }) @Get('/:provider/repositories') async listRepositoriesByProvider(@Param('provider') provider: string) { return this.repoService.listRepositoriesByProvider(provider); @@ -20,6 +21,8 @@ export class RepoController { @ApiOperation({ summary: 'Get a list of available branches' }) @Get('/:provider/:gitrepob64/branches') + @ApiParam({ name: "provider", type: "string", description: "A git provider", required: true, enum: ['github', 'gitlab', 'bigbucket', 'gitea', 'gogs'] }) + @ApiParam({ name: "gitrepob64", type: "string", description: "A base64 encoded repository URL", required: true }) async listBranches( @Param('provider') provider: string, @Param('gitrepob64') gitrepob64: string, @@ -29,6 +32,8 @@ export class RepoController { @ApiOperation({ summary: 'Get a list of available Pull requests' }) @Get('/:provider/:gitrepob64/pullrequests') + @ApiParam({ name: "provider", type: "string", description: "A git provider", required: true, enum: ['github', 'gitlab', 'bigbucket', 'gitea', 'gogs'] }) + @ApiParam({ name: "gitrepob64", type: "string", description: "A base64 encoded repository URL", required: true }) async listPullRequests( @Param('provider') provider: string, @Param('gitrepob64') gitrepob64: string, @@ -38,6 +43,8 @@ export class RepoController { @ApiOperation({ summary: 'Get a list of all available references' }) @Get('/:provider/:gitrepob64/references') + @ApiParam({ name: "provider", type: "string", description: "A git provider", required: true, enum: ['github', 'gitlab', 'bigbucket', 'gitea', 'gogs'] }) + @ApiParam({ name: "gitrepob64", type: "string", description: "A base64 encoded repository URL", required: true }) async listReferences( @Param('provider') provider: string, @Param('gitrepob64') gitrepob64: string, @@ -45,20 +52,23 @@ export class RepoController { return this.repoService.listReferences(provider, gitrepob64); } - @Post('/:provider/connect') @ApiOperation({ summary: 'Connect a repository' }) + @Post('/:provider/connect') + @ApiParam({ name: "provider", type: "string", description: "A git provider", required: true, enum: ['github', 'gitlab', 'bigbucket', 'gitea', 'gogs'] }) async connectRepo(@Param('provider') provider: string, @Body() body: any) { return this.repoService.connectRepo(provider, body.gitrepo); } @ApiOperation({ summary: 'Disconnect a repository' }) @Post('/:provider/disconnect') + @ApiParam({ name: "provider", type: "string", description: "A git provider", required: true, enum: ['github', 'gitlab', 'bigbucket', 'gitea', 'gogs'] }) async disconnectRepo(@Param('provider') provider: string, @Body() body: any) { return this.repoService.disconnectRepo(provider, body.gitrepo); } @ApiOperation({ summary: 'Webhooks endpoint for repository providers' }) @Post('/repo/webhooks/:provider') + @ApiParam({ name: "provider", type: "string", description: "A git provider", required: true, enum: ['github', 'gitlab', 'bigbucket', 'gitea', 'gogs'] }) async repositoryWebhook(@Body() body: any) { return 'Not implemented'; } diff --git a/server-refactored-v3/src/templates/templates.controller.spec.ts b/server-refactored-v3/src/templates/templates.controller.spec.ts new file mode 100644 index 00000000..7017523b --- /dev/null +++ b/server-refactored-v3/src/templates/templates.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TemplatesController } from './templates.controller'; + +describe('TemplatesController', () => { + let controller: TemplatesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [TemplatesController], + }).compile(); + + controller = module.get(TemplatesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/templates/templates.controller.ts b/server-refactored-v3/src/templates/templates.controller.ts new file mode 100644 index 00000000..fe61bb84 --- /dev/null +++ b/server-refactored-v3/src/templates/templates.controller.ts @@ -0,0 +1,28 @@ +import { Controller, Get, Logger, Param, Res } from '@nestjs/common'; +import { ApiOperation, ApiParam } from '@nestjs/swagger'; +import { TemplatesService } from './templates.service'; +import { Response } from 'express'; + +@Controller({ path: 'api/templates', version: '1' }) +export class TemplatesController { + private readonly logger = new Logger(TemplatesController.name); + constructor( + private readonly templatesService: TemplatesService, + ) {} + + @ApiOperation({ summary: 'Load a specific template' }) + @Get('/:templateB64') + @ApiParam({ name: "templateB64", type: "string", description: "A base64 encoded URL", required: true }) + async getTemplate( + @Param('templateB64') templateB64: string, + @Res() res: Response + ) { + try { + const template = await this.templatesService.getTemplate(templateB64); + res.send(template); + } catch (err) { + this.logger.error(err); + res.status(500).send(err); + } + } +} diff --git a/server-refactored-v3/src/templates/templates.service.spec.ts b/server-refactored-v3/src/templates/templates.service.spec.ts new file mode 100644 index 00000000..811e09ab --- /dev/null +++ b/server-refactored-v3/src/templates/templates.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TemplatesService } from './templates.service'; + +describe('TemplatesService', () => { + let service: TemplatesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [TemplatesService], + }).compile(); + + service = module.get(TemplatesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server-refactored-v3/src/templates/templates.service.ts b/server-refactored-v3/src/templates/templates.service.ts new file mode 100644 index 00000000..1bd02b88 --- /dev/null +++ b/server-refactored-v3/src/templates/templates.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import YAML from 'yaml'; + +@Injectable() +export class TemplatesService { + + private YAML = require('yaml'); + constructor() {} + + async getTemplate(templateB64: string) { + + // decode the base64 encoded URL + const templateUrl = Buffer.from(templateB64, 'base64').toString('ascii'); + + const template = await axios.get(templateUrl) + .catch((err) => { + throw new Error(err); + }); + if (template) { + const ret = this.YAML.parse(template.data); + return ret.spec; + } + } +} From dcd99b7e3777e3a937fff5eea4bbd1368c631ee3 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Thu, 13 Feb 2025 21:58:58 +0100 Subject: [PATCH 44/65] Migrated container console --- client/src/components/apps/console.vue | 4 +- .../src/apps/apps.controller.ts | 33 ++++++ .../src/apps/apps.interface.ts | 21 ++++ server-refactored-v3/src/apps/apps.service.ts | 112 +++++++++++++++++- .../src/deployments/deployments.module.ts | 2 +- .../src/events/events.gateway.ts | 18 +++ .../src/events/events.module.ts | 4 +- server-refactored-v3/src/logs/logs.module.ts | 2 +- .../src/notifications/notifications.module.ts | 1 - server/src/kubero.ts | 3 + 10 files changed, 192 insertions(+), 8 deletions(-) diff --git a/client/src/components/apps/console.vue b/client/src/components/apps/console.vue index 37c7981e..6d5e5808 100644 --- a/client/src/components/apps/console.vue +++ b/client/src/components/apps/console.vue @@ -109,7 +109,7 @@ export default defineComponent({ }), methods: { loadPods() { - axios.get(`/api/status/pods/${this.pipeline}/${this.phase}/${this.app}`).then((response) => { + axios.get(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/pods`).then((response) => { //this.loadContainers(); for (let pod of response.data) { const p = {name: pod.name, containers: pod.containers.map((c: any) => c.name)} as Pod; @@ -258,7 +258,7 @@ export default defineComponent({ }); }, execInContainer(){ - axios.post(`/api/console/${this.pipeline}/${this.phase}/${this.app}/exec`, { + axios.post(`/api/apps/${this.pipeline}/${this.phase}/${this.app}/console`, { podName: this.pod.name, containerName: this.container, command: this.command, diff --git a/server-refactored-v3/src/apps/apps.controller.ts b/server-refactored-v3/src/apps/apps.controller.ts index 308b1703..ca5f0bbf 100644 --- a/server-refactored-v3/src/apps/apps.controller.ts +++ b/server-refactored-v3/src/apps/apps.controller.ts @@ -147,4 +147,37 @@ export class AppsController { return this.appsService.restartApp(pipeline, phase, app, user); } + + @ApiOperation({ summary: 'Get the app pods' }) + @Get('/:pipeline/:phase/:app/pods') + async getPods( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + ) { + return this.appsService.getPods(pipeline, phase, app); + } + + @ApiOperation({ summary: 'Start a container console' }) + @Post('/:pipeline/:phase/:app/console') + async execInContainer( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Body() body: any, + ) { + + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890', + }; + + const podName = body.podName; + const containerName = body.containerName; + const command = body.command; + + return this.appsService.execInContainer(pipeline, phase, app, podName, containerName, command, user); + } } diff --git a/server-refactored-v3/src/apps/apps.interface.ts b/server-refactored-v3/src/apps/apps.interface.ts index 2a3347b0..1ee55f99 100644 --- a/server-refactored-v3/src/apps/apps.interface.ts +++ b/server-refactored-v3/src/apps/apps.interface.ts @@ -172,3 +172,24 @@ export interface ICronjob { image: string; imagePullPolicy: string; } + +export interface Workload { + name: string, + namespace: string, + phase: string, + pipeline: string, + status: string, + restarts: number, + age: Date | undefined, + startTime: Date | undefined, + containers: WorkloadContainer[] +} + +export interface WorkloadContainer { + name: string, + image: string, + restartCount?: number, + ready?: boolean, + started?: boolean, + age: Date | undefined, +} diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index 96725eca..80a0957e 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -5,12 +5,13 @@ import { NotificationsService } from '../notifications/notifications.service'; import { IKubectlApp } from '../kubernetes/kubernetes.interface'; import { INotification } from '../notifications/notifications.interface'; import { App } from './app/app'; -import { IApp } from './apps.interface'; +import { IApp, Workload, WorkloadContainer } from './apps.interface'; import { IPipelineList } from '../pipelines/pipelines.interface'; import { IUser } from '../auth/auth.interface'; import { SettingsService } from 'src/settings/settings.service'; -//import YAML from 'yaml'; import { KubectlTemplate } from 'src/templates/template'; +import { Stream } from 'stream'; +import { EventsGateway } from 'src/events/events.gateway'; @Injectable() export class AppsService { @@ -22,6 +23,7 @@ export class AppsService { private pipelinesService: PipelinesService, private NotificationsService: NotificationsService, private settingsService: SettingsService, + private eventsGateway: EventsGateway, ) { this.logger.log('AppsService initialized'); } @@ -522,4 +524,110 @@ export class AppsService { this.NotificationsService.send(m); } } + + public async getPods(pipelineName: string, phaseName: string, appName: string): Promise { + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + const namespace = pipelineName+'-'+phaseName; + + let workloads: Workload[] = []; + + if (contextName) { + this.kubectl.setCurrentContext(contextName); + const workload = await this.kubectl.getPods(namespace, contextName); + //return workload + for (const pod of workload) { + // check if app label name starts with appName + if (!pod.metadata?.generateName?.startsWith(appName+'-kuberoapp')) { + continue; + } + + let workload = { + name: pod.metadata?.name, + namespace: pod.metadata?.namespace, + phase: phaseName, + pipeline: pipelineName, + status: pod.status?.phase, + age: pod.metadata?.creationTimestamp, + startTime: pod.status?.startTime, + containers: [] as WorkloadContainer[], + } as Workload; + + //for (const container of pod.spec?.containers || []) { + const containersCount = pod.spec?.containers?.length || 0; + for (let i = 0; i < containersCount; i++) { + workload.containers.push({ + name: pod.spec?.containers[i].name, + image: pod.spec?.containers[i].image, + restartCount: pod.status?.containerStatuses?.[i]?.restartCount, + ready: pod.status?.containerStatuses?.[i]?.ready, + started: pod.status?.containerStatuses?.[i]?.started, + } as WorkloadContainer); + } + + workloads.push(workload); + } + } + return workloads; + } + + public async execInContainer(pipelineName: string, phaseName: string, appName: string, podName: string, containerName: string, command: string, user: IUser) { + + /*TODO: Fails. Needs to be loaded somewhere + const settings = await this.settingsService.getSettings(); + console.log(settings.kubero?.console?.enabled) + if (settings.kubero?.console?.enabled != true) { + this.logger.warning('Warning: console is nost set or disabled in config'); + return; + } + */ + + const contextName = await this.pipelinesService.getContext(pipelineName, phaseName); + if (contextName) { + const streamname = `${pipelineName}-${phaseName}-${appName}-${podName}-${containerName}-terminal`; + + if ( process.env.KUBERO_READONLY == 'true'){ + console.log('KUBERO_READONLY is set to true, terminal access not allowed'); + return; + } + + if ( this.eventsGateway.execStreams[streamname] ) { + if (this.eventsGateway.execStreams[streamname].websocket.readyState == this.eventsGateway.execStreams[streamname].websocket.OPEN) { + console.log('execInContainer: execStream already running'); + return; + } else { + console.log('CLOSED', this.eventsGateway.execStreams[streamname].websocket.CLOSED) + console.log('execInContainer: execStream already running but not open, deleting :', this.eventsGateway.execStreams[streamname].websocket.readyState); + delete this.eventsGateway.execStreams[streamname]; + + // wait a bit to make sure the stream is closed + await new Promise(resolve => setTimeout(resolve, 3000)); + } + } + + const execStream = new Stream.PassThrough(); + + const namespace = pipelineName+'-'+phaseName; + const ws = await this.kubectl.execInContainer(namespace, podName, containerName, command, execStream) + .catch(error => { + console.log(error); + return; + }); + + if (!ws || ws.readyState != ws.OPEN) { + console.log('execInContainer: ws is undefined or not open'); + return; + } + + let stream = { + websocket: ws as unknown as WebSocket, + stream: execStream + }; + this.eventsGateway.execStreams[streamname] = stream; + + // sending the terminal output to the client + ws.on('message', (data: Buffer) => { + this.eventsGateway.sendTerminalLine(streamname, data.toString()); + }); + } + } } diff --git a/server-refactored-v3/src/deployments/deployments.module.ts b/server-refactored-v3/src/deployments/deployments.module.ts index 05677023..67f8a781 100644 --- a/server-refactored-v3/src/deployments/deployments.module.ts +++ b/server-refactored-v3/src/deployments/deployments.module.ts @@ -7,6 +7,6 @@ import { LogsService } from 'src/logs/logs.service'; @Module({ controllers: [DeploymentsController], - providers: [DeploymentsService, AppsService, EventsGateway, LogsService], + providers: [DeploymentsService, AppsService, LogsService], }) export class DeploymentsModule {} diff --git a/server-refactored-v3/src/events/events.gateway.ts b/server-refactored-v3/src/events/events.gateway.ts index 4426ed83..9cf67534 100644 --- a/server-refactored-v3/src/events/events.gateway.ts +++ b/server-refactored-v3/src/events/events.gateway.ts @@ -19,6 +19,7 @@ import { Server, Socket } from 'socket.io'; export class EventsGateway { @WebSocketServer() server: Server; + public execStreams: {[key: string]: {websocket: WebSocket, stream: any}} = {}; constructor() { //Logger.debug('EventsGateway created'); @@ -50,4 +51,21 @@ export class EventsGateway { //TODO define logline type this.server.to(room).emit('log', logline); } + + // sending the terminal input to the server + @SubscribeMessage('terminal') + handleTerminal( + @MessageBody() data: any, + @ConnectedSocket() client: Socket, + ): void { + + if (this.execStreams[data.room]) { + this.execStreams[data.room].stream.write(data.data); + } + } + + // sending the terminal output to the client + sendTerminalLine(room: string, line: string) { + this.server.to(room).emit('consoleresponse', line); + } } diff --git a/server-refactored-v3/src/events/events.module.ts b/server-refactored-v3/src/events/events.module.ts index 2b2d1cbb..1e723d35 100644 --- a/server-refactored-v3/src/events/events.module.ts +++ b/server-refactored-v3/src/events/events.module.ts @@ -1,7 +1,9 @@ -import { Module } from '@nestjs/common'; +import { Module, Global } from '@nestjs/common'; import { EventsGateway } from './events.gateway'; +@Global() @Module({ providers: [EventsGateway], + exports: [EventsGateway], }) export class EventsModule {} diff --git a/server-refactored-v3/src/logs/logs.module.ts b/server-refactored-v3/src/logs/logs.module.ts index 99988fab..84712fe2 100644 --- a/server-refactored-v3/src/logs/logs.module.ts +++ b/server-refactored-v3/src/logs/logs.module.ts @@ -5,7 +5,7 @@ import { AppsService } from 'src/apps/apps.service'; import { LogsController } from './logs.controller'; @Module({ - providers: [LogsService, EventsGateway, AppsService], + providers: [LogsService, AppsService], controllers: [LogsController], }) export class LogsModule {} diff --git a/server-refactored-v3/src/notifications/notifications.module.ts b/server-refactored-v3/src/notifications/notifications.module.ts index bfdf5503..f5393107 100644 --- a/server-refactored-v3/src/notifications/notifications.module.ts +++ b/server-refactored-v3/src/notifications/notifications.module.ts @@ -8,7 +8,6 @@ import { KubernetesModule } from '../kubernetes/kubernetes.module'; @Module({ providers: [ NotificationsService, - EventsGateway, AuditModule, KubernetesModule, ], diff --git a/server/src/kubero.ts b/server/src/kubero.ts index d6036af1..a128b123 100644 --- a/server/src/kubero.ts +++ b/server/src/kubero.ts @@ -63,6 +63,7 @@ export class Kubero { this.kubectl = kubectl; this.notification = notifications + //Migrated to events this._io.on('connection', client => { client.on('terminal', (data: any) => { //console.log('terminal input', data.data); @@ -989,6 +990,7 @@ export class Kubero { return this.config.kubero?.admin?.disabled; } + //Migrated to apps public async execInContainer(pipelineName: string, phaseName: string, appName: string, podName: string, containerName: string, command: string, user: User) { console.log(this.config.kubero?.console.enabled) if (this.config.kubero?.console.enabled != true) { @@ -1533,6 +1535,7 @@ export class Kubero { } } + // Migrated to apps public async getPods(pipelineName: string, phaseName: string, appName: string): Promise { const contextName = this.getContext(pipelineName, phaseName); const namespace = pipelineName+'-'+phaseName; From 1008ac20263d8f7c3457dfa2269f25059c2908be Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Fri, 14 Feb 2025 02:17:31 +0100 Subject: [PATCH 45/65] Migrate build Jobs --- server-refactored-v3/src/apps/apps.module.ts | 1 + server-refactored-v3/src/apps/apps.service.ts | 4 +- .../src/deployments/deployments.controller.ts | 70 ++++++++- .../src/deployments/deployments.module.ts | 5 +- .../src/deployments/deployments.service.ts | 10 +- .../src/deployments/dto/CreateBuild.dto.ts | 16 ++ .../src/deployments/templates/buildpacks.yaml | 139 +++++++++++++++++ .../src/deployments/templates/dockerfile.yaml | 124 ++++++++++++++++ .../src/deployments/templates/nixpacks.yaml | 140 ++++++++++++++++++ .../src/kubernetes/kubernetes.service.ts | 15 +- server-refactored-v3/src/logs/logs.service.ts | 5 +- .../templates/buildpacks.yaml | 139 +++++++++++++++++ .../templates/dockerfile.yaml | 124 ++++++++++++++++ server-refactored-v3/templates/nixpacks.yaml | 140 ++++++++++++++++++ 14 files changed, 918 insertions(+), 14 deletions(-) create mode 100644 server-refactored-v3/src/deployments/dto/CreateBuild.dto.ts create mode 100644 server-refactored-v3/src/deployments/templates/buildpacks.yaml create mode 100644 server-refactored-v3/src/deployments/templates/dockerfile.yaml create mode 100644 server-refactored-v3/src/deployments/templates/nixpacks.yaml create mode 100644 server-refactored-v3/templates/buildpacks.yaml create mode 100644 server-refactored-v3/templates/dockerfile.yaml create mode 100644 server-refactored-v3/templates/nixpacks.yaml diff --git a/server-refactored-v3/src/apps/apps.module.ts b/server-refactored-v3/src/apps/apps.module.ts index b547b3f2..ddb9df37 100644 --- a/server-refactored-v3/src/apps/apps.module.ts +++ b/server-refactored-v3/src/apps/apps.module.ts @@ -3,6 +3,7 @@ import { AppsService } from './apps.service'; import { KubernetesModule } from '../kubernetes/kubernetes.module'; import { AppsController } from './apps.controller'; import { PipelinesService } from '../pipelines/pipelines.service'; +//import { DeploymentsService } from 'src/deployments/deployments.service'; @Module({ providers: [AppsService, KubernetesModule, PipelinesService], diff --git a/server-refactored-v3/src/apps/apps.service.ts b/server-refactored-v3/src/apps/apps.service.ts index 80a0957e..79a2f2f1 100644 --- a/server-refactored-v3/src/apps/apps.service.ts +++ b/server-refactored-v3/src/apps/apps.service.ts @@ -33,9 +33,7 @@ export class AppsService { phaseName: string, appName: string, ) { - this.logger.debug( - 'get App: ' + appName + ' in ' + pipelineName + ' phase: ' + phaseName, - ); + this.logger.debug('get App: ' + appName + ' in ' + pipelineName + ' phase: ' + phaseName); const contextName = await this.pipelinesService.getContext( pipelineName, phaseName, diff --git a/server-refactored-v3/src/deployments/deployments.controller.ts b/server-refactored-v3/src/deployments/deployments.controller.ts index 660b6bc0..e8ffd504 100644 --- a/server-refactored-v3/src/deployments/deployments.controller.ts +++ b/server-refactored-v3/src/deployments/deployments.controller.ts @@ -1,12 +1,17 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; import { DeploymentsService } from './deployments.service'; -import { ApiOperation } from '@nestjs/swagger'; +import { ApiBody, ApiOperation, ApiParam } from '@nestjs/swagger'; +import { IUser } from 'src/auth/auth.interface'; +import { CreateBuild } from './dto/CreateBuild.dto'; @Controller({ path: 'api/deployments', version: '1' }) export class DeploymentsController { constructor(private readonly deploymentsService: DeploymentsService) {} @ApiOperation({ summary: 'List deployments for a specific app' }) + @ApiParam({ name: 'pipeline', description: 'Pipeline name' }) + @ApiParam({ name: 'phase', description: 'Phase name' }) + @ApiParam({ name: 'app', description: 'App name' }) @Get('/:pipeline/:phase/:app') async getDeployments( @Param('pipeline') pipeline: string, @@ -15,4 +20,65 @@ export class DeploymentsController { ) { return this.deploymentsService.listBuildjobs(pipeline, phase, app); } + + @ApiOperation({ summary: 'Build a specific app' }) + @ApiParam({ name: 'pipeline', description: 'Pipeline name' }) + @ApiParam({ name: 'phase', description: 'Phase name' }) + @ApiParam({ name: 'app', description: 'App name' }) + @ApiBody({ type: CreateBuild, required: true, schema: { $ref: '#/components/schemas/CreateBuild' } }) + @Post('/build/:pipeline/:phase/:app') + async buildApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Body() body: CreateBuild, + ) { + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890', + }; + return this.deploymentsService.triggerBuildjob(pipeline, phase, app, body.buildstrategy, body.repository, body.reference, body.dockerfilePath, user); + } + + @ApiOperation({ summary: 'Delete a specific app' }) + @ApiParam({ name: 'pipeline', description: 'Pipeline name' }) + @ApiParam({ name: 'phase', description: 'Phase name' }) + @ApiParam({ name: 'app', description: 'App name' }) + @ApiParam({ name: 'buildName', description: 'Build name' }) + @Delete('/:pipeline/:phase/:app/:buildName') + async deleteApp( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Param('buildName') buildName: string, + ) { + //TODO: Migration -> this is a mock user + const user: IUser = { + id: 1, + method: 'local', + username: 'admin', + apitoken: '1234567890', + }; + return this.deploymentsService.deleteBuildjob(pipeline, phase, app, buildName, user); + } + + @ApiOperation({ summary: 'Get logs for a specific app' }) + @ApiParam({ name: 'pipeline', description: 'Pipeline name' }) + @ApiParam({ name: 'phase', description: 'Phase name' }) + @ApiParam({ name: 'app', description: 'App name' }) + @ApiParam({ name: 'build', description: 'Build name' }) + @ApiParam({ name: 'container', description: 'Container name' }) + @Get('/:pipeline/:phase/:app/:build/:container/history') + async getLogs( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Param('build') build: string, + @Param('container') container: string, + ) { + return this.deploymentsService.getBuildLogs(pipeline, phase, app, build, container); + } } diff --git a/server-refactored-v3/src/deployments/deployments.module.ts b/server-refactored-v3/src/deployments/deployments.module.ts index 67f8a781..83287ff5 100644 --- a/server-refactored-v3/src/deployments/deployments.module.ts +++ b/server-refactored-v3/src/deployments/deployments.module.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; import { DeploymentsController } from './deployments.controller'; import { DeploymentsService } from './deployments.service'; -import { AppsService } from 'src/apps/apps.service'; -import { EventsGateway } from 'src/events/events.gateway'; -import { LogsService } from 'src/logs/logs.service'; +import { AppsService } from '../apps/apps.service'; +import { LogsService } from '../logs/logs.service'; @Module({ controllers: [DeploymentsController], diff --git a/server-refactored-v3/src/deployments/deployments.service.ts b/server-refactored-v3/src/deployments/deployments.service.ts index 414a16f3..54567f1b 100644 --- a/server-refactored-v3/src/deployments/deployments.service.ts +++ b/server-refactored-v3/src/deployments/deployments.service.ts @@ -6,10 +6,11 @@ import { NotificationsService } from '../notifications/notifications.service'; import { INotification } from '../notifications/notifications.interface'; import { IUser } from '../auth/auth.interface'; import { AppsService } from '../apps/apps.service'; -import { V1JobList } from '@kubernetes/client-node'; import { PipelinesService } from '../pipelines/pipelines.service'; import { ILoglines } from '../logs/logs.interface'; import { LogsService } from '../logs/logs.service'; +import { V1JobList } from '@kubernetes/client-node'; + @Injectable() export class DeploymentsService { @@ -17,6 +18,8 @@ export class DeploymentsService { //private notification: Notifications; //private kubero: Kubero; + private YAML = require('yaml'); + constructor( //options: DeploymentOptions private kubectl: KubernetesService, @@ -118,6 +121,9 @@ export class DeploymentsService { dockerfilePath: string, user: IUser, ): Promise { + + //this.logger.debug('triggerBuildjob: ' + pipeline + ' ' + phase + ' ' + app + ' ' + buildstrategy + ' ' + gitrepo + ' ' + reference + ' ' + dockerfilePath + ' ' + user.username); + if (process.env.KUBERO_READONLY == 'true') { this.logger.log( 'KUBERO_READONLY is set to true, not triggering build for app: ' + @@ -135,7 +141,7 @@ export class DeploymentsService { return; } - // Create the Pipeline CRD + // Create the Build CRD try { await this.kubectl.createBuildJob( namespace, diff --git a/server-refactored-v3/src/deployments/dto/CreateBuild.dto.ts b/server-refactored-v3/src/deployments/dto/CreateBuild.dto.ts new file mode 100644 index 00000000..587219dc --- /dev/null +++ b/server-refactored-v3/src/deployments/dto/CreateBuild.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class CreateBuild { + + @ApiProperty( { enum: ["buildpacks", "dockerfile", "nixpacks", "plain"] } ) + buildstrategy: "buildpacks" | "dockerfile" | "nixpacks" | "plain" + + @ApiProperty() + repository: string + + @ApiProperty() + reference: string + + @ApiProperty() + dockerfilePath: string +} diff --git a/server-refactored-v3/src/deployments/templates/buildpacks.yaml b/server-refactored-v3/src/deployments/templates/buildpacks.yaml new file mode 100644 index 00000000..f5b706cb --- /dev/null +++ b/server-refactored-v3/src/deployments/templates/buildpacks.yaml @@ -0,0 +1,139 @@ +--- +# Source: kuberobuild/templates/job-buikdpacks.yaml +apiVersion: batch/v1 +kind: Job +metadata: + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: buildpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + name: example-test-20240631-2237 +spec: + ttlSecondsAfterFinished: 31536000 + backoffLimit: 0 # 0 means do not retry + completionMode: NonIndexed + completions: 1 + manualSelector: false + parallelism: 1 + podReplacementPolicy: TerminatingOrFailed + suspend: false + template: + metadata: + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: buildpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + spec: + automountServiceAccountToken: true + securityContext: + fsGroup: 1000 + containers: + - name: deploy + env: + - name: REPOSITORY + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app + - name: TAG + value: "123456" + - name: APP + value: example + command: + - sh + - -c + - 'kubectl patch kuberoapps $APP --type=merge -p "{\"spec\":{\"image\":{\"repository\": + \"$REPOSITORY\",\"tag\": \"$TAG\"}}}"' + image: bitnami/kubectl:latest + imagePullPolicy: Always + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + initContainers: + - name: fetch + env: + - name: GIT_REPOSITORY + value: git@github.com:kubero-dev/template-nodeapp.git + - name: GIT_REF + value: main + - name: KUBERO_BUILDPACK_DEFAULT_RUN_CMD + value: "exit 0" + - name: KUBERO_BUILDPACK_DEFAULT_BUILD_CMD + value: "exit 0" + image: "ghcr.io/kubero-dev/fetch:latest" + imagePullPolicy: Always + resources: {} + securityContext: + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/kubero/.ssh-mounted + name: deployment-keys + readOnly: true + - mountPath: /app + name: app-storage + workingDir: /app + - command: + - sh + - -c + - chmod -R g+w /app + image: busybox:latest + imagePullPolicy: IfNotPresent + name: permissions + securityContext: + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /app + name: app-storage + workingDir: /app + - name: build + args: + - '-app=.' + - registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app:mytag-id + command: ['/cnb/lifecycle/creator'] + # https://github.com/buildpacks/pack/issues/564#issuecomment-943345649 + # https://github.com/buildpacks/spec/blob/platform/v0.13/platform.md#creator + #command: ['/cnb/lifecycle/creator', '-app=.', '-buildpacks=/cnb/buildpacks', '-platform=/platform', '-run-image=ghcr.io/kubero-dev/run:v1.4.0', '-uid=1000', '-gid=1000', 'kubero-local-dev-0037732.loca.lt/example/exampled:latest'] + #command: ['tail', '-f', '/dev/null'] + image: "paketobuildpacks/builder-jammy-full:latest" #List of Builders : https://paketo.io/docs/reference/builders-reference/ + imagePullPolicy: Always + resources: {} + env: + - name: CNB_PLATFORM_API + value: "0.13" + securityContext: + privileged: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + readOnly: false + - mountPath: /home/cnb/.docker + name: docker-config + readOnly: true + workingDir: /app + restartPolicy: Never + schedulerName: default-scheduler + serviceAccount: example-kuberoapp + serviceAccountName: example-kuberoapp + terminationGracePeriodSeconds: 30 + volumes: + - name: deployment-keys + secret: +# defaultMode: 420 + secretName: deployment-keys + - emptyDir: {} + name: app-storage + - name: docker-config + secret: + secretName: kubero-pull-secret + items: + - key: .dockerconfigjson + path: config.json +# - name: pull-secret +# secret: +# defaultMode: 0384 +# secretName: kubero-pull-secret \ No newline at end of file diff --git a/server-refactored-v3/src/deployments/templates/dockerfile.yaml b/server-refactored-v3/src/deployments/templates/dockerfile.yaml new file mode 100644 index 00000000..e0de68e0 --- /dev/null +++ b/server-refactored-v3/src/deployments/templates/dockerfile.yaml @@ -0,0 +1,124 @@ +--- +# Source: kuberobuild/templates/job-dockerfile.yaml +apiVersion: batch/v1 +kind: Job +metadata: + generation: 1 + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: dockerfile + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + name: example-test-20240631-2237 +spec: + ttlSecondsAfterFinished: 31536000 + backoffLimit: 0 # 0 means do not retry + completionMode: NonIndexed + completions: 1 + manualSelector: false + parallelism: 1 + podReplacementPolicy: TerminatingOrFailed + suspend: false + template: + metadata: + creationTimestamp: null + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: dockerfile + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + spec: + automountServiceAccountToken: true + securityContext: + fsGroup: 1000 + containers: + - env: + - name: REPOSITORY + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app + - name: TAG + value: 123456 + - name: APP + value: example + command: + - sh + - -c + - 'kubectl patch kuberoapps $APP --type=merge -p "{\"spec\":{\"image\":{\"repository\": + \"$REPOSITORY\",\"tag\": \"$TAG\"}}}"' + image: bitnami/kubectl:latest + imagePullPolicy: Always + name: deploy + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + initContainers: + - name: fetch + env: + - name: GIT_REPOSITORY + value: git@github.com:kubero-dev/template-nodeapp.git + - name: GIT_REF + value: main + - name: KUBERO_BUILDPACK_DEFAULT_RUN_CMD + value: "exit 0" + - name: KUBERO_BUILDPACK_DEFAULT_BUILD_CMD + value: "exit 0" + image: "ghcr.io/kubero-dev/fetch:latest" + imagePullPolicy: Always + resources: {} + securityContext: + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/kubero/.ssh-mounted + name: deployment-keys + readOnly: true + - mountPath: /app + name: app-storage + workingDir: /app + - name: push + command: + - sh + - -c + - |- + buildah build -f $BUILDAH_DOCKERFILE_PATH --isolation chroot -t $BUILD_IMAGE . + buildah push --tls-verify=false $BUILD_IMAGE + env: + - name: REGISTRY_AUTH_FILE + value: /etc/buildah/auth/.dockerconfigjson + - name: BUILD_IMAGE + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app:123456 + - name: BUILDAH_DOCKERFILE_PATH + value: /app/Dockerfile + image: "quay.io/containers/buildah:v1.35" + imagePullPolicy: IfNotPresent + resources: {} + securityContext: + privileged: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + readOnly: true + - mountPath: /etc/buildah/auth + name: pull-secret + readOnly: true + workingDir: /app + restartPolicy: Never + schedulerName: default-scheduler + serviceAccount: example-kuberoapp + serviceAccountName: example-kuberoapp + terminationGracePeriodSeconds: 30 + volumes: + - name: deployment-keys + secret: +# defaultMode: 420 + secretName: deployment-keys + - emptyDir: {} + name: app-storage + - name: pull-secret + secret: + defaultMode: 384 + secretName: kubero-pull-secret \ No newline at end of file diff --git a/server-refactored-v3/src/deployments/templates/nixpacks.yaml b/server-refactored-v3/src/deployments/templates/nixpacks.yaml new file mode 100644 index 00000000..ce2ae540 --- /dev/null +++ b/server-refactored-v3/src/deployments/templates/nixpacks.yaml @@ -0,0 +1,140 @@ +--- +# Source: kuberobuild/templates/job-nixpack.yaml +apiVersion: batch/v1 +kind: Job +metadata: + generation: 1 + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: nixpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + name: example-test-20240631-2237 +spec: + ttlSecondsAfterFinished: 31536000 + backoffLimit: 0 # 0 means do not retry + completionMode: NonIndexed + completions: 1 + manualSelector: false + parallelism: 1 + podReplacementPolicy: TerminatingOrFailed + suspend: false + template: + metadata: + creationTimestamp: null + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: nixpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + spec: + automountServiceAccountToken: true + securityContext: + fsGroup: 1000 + containers: + - env: + - name: REPOSITORY + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app + - name: TAG + value: 123456 + - name: APP + value: example + command: + - sh + - -c + - 'kubectl patch kuberoapps $APP --type=merge -p "{\"spec\":{\"image\":{\"repository\": + \"$REPOSITORY\",\"tag\": \"$TAG\"}}}"' + image: bitnami/kubectl:latest + imagePullPolicy: Always + name: deploy + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + initContainers: + - name: fetch + env: + - name: GIT_REPOSITORY + value: git@github.com:kubero-dev/template-nodeapp.git + - name: GIT_REF + value: main + - name: KUBERO_BUILDPACK_DEFAULT_RUN_CMD + value: "exit 0" + - name: KUBERO_BUILDPACK_DEFAULT_BUILD_CMD + value: "exit 0" + image: "ghcr.io/kubero-dev/fetch:latest" + imagePullPolicy: Always + resources: {} + securityContext: + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/kubero/.ssh-mounted + name: deployment-keys + readOnly: true + - mountPath: /app + name: app-storage + workingDir: /app + - name: build + command: + - sh + - -c + - nixpacks build . -o . + image: "ghcr.io/kubero-dev/build:latest" + imagePullPolicy: Always + resources: {} + securityContext: + privileged: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + workingDir: /app + - name: push + command: + - sh + - -c + - |- + buildah build -f $BUILDAH_DOCKERFILE_PATH --isolation chroot -t $BUILD_IMAGE . + buildah push --tls-verify=false $BUILD_IMAGE + env: + - name: REGISTRY_AUTH_FILE + value: /etc/buildah/auth/.dockerconfigjson + - name: BUILD_IMAGE + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app:123456 + - name: BUILDAH_DOCKERFILE_PATH + value: /app/Dockerfile + image: "quay.io/containers/buildah:v1.35" + imagePullPolicy: IfNotPresent + resources: {} + securityContext: + privileged: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + readOnly: true + - mountPath: /etc/buildah/auth + name: pull-secret + readOnly: true + workingDir: /app + restartPolicy: Never + schedulerName: default-scheduler + serviceAccount: example-kuberoapp + serviceAccountName: example-kuberoapp + terminationGracePeriodSeconds: 30 + volumes: + - name: deployment-keys + secret: +# defaultMode: 420 + secretName: deployment-keys + - emptyDir: {} + name: app-storage + - name: pull-secret + secret: + defaultMode: 384 + secretName: kubero-pull-secret \ No newline at end of file diff --git a/server-refactored-v3/src/kubernetes/kubernetes.service.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts index 2c30877d..95eb24ad 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.service.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -41,6 +41,8 @@ import { WebSocket } from 'ws'; import stream from 'stream'; import internal from 'stream'; import { IKuberoConfig, IKuberoCRD } from 'src/settings/settings.interface'; +import { join } from 'path'; +import { readFileSync } from 'fs'; @Injectable() export class KubernetesService { @@ -60,6 +62,7 @@ export class KubernetesService { //public config: IKuberoConfig; private exec: Exec = {} as Exec; private readonly logger = new Logger(KubernetesService.name); + private YAML = require('yaml'); constructor() { this.kc = new KubeConfig(); @@ -1249,10 +1252,11 @@ export class KubernetesService { image: string; tag: string; }, + //job: any, //V1Job, ): Promise { this.logger.error('refactoring: loadJob not implemented'); - //let job = loadJob(buildstrategy) as any - const job = new Object() as any; + let job = this.loadJob(buildstrategy) as any + //const job = new Object() as any; const id = new Date() .toISOString() @@ -1436,4 +1440,11 @@ export class KubernetesService { this.logger.error('ERROR creating namespace'); } } + + private loadJob(jobname: string): V1Job { + const path = join(__dirname, `../../templates/${jobname}.yaml`) + this.logger.debug(`loading job from ${path}`) + const job = readFileSync( path, 'utf8') + return this.YAML.parse(job) as V1Job + } } diff --git a/server-refactored-v3/src/logs/logs.service.ts b/server-refactored-v3/src/logs/logs.service.ts index 3c3c7efc..b75cc736 100644 --- a/server-refactored-v3/src/logs/logs.service.ts +++ b/server-refactored-v3/src/logs/logs.service.ts @@ -80,7 +80,8 @@ export class LogsService { this.podLogStreams.push(podName); }) .catch((err) => { - this.logger.debug(err); + this.logger.error('Failed to start logs for ' + podName + ' ' + container); + this.logger.error(err.body.message); }); } else { this.logger.debug('logs already running ' + podName + ' ' + container); @@ -102,7 +103,7 @@ export class LogsService { if (contextName) { this.kubectl.getPods(namespace, contextName).then((pods: any[]) => { for (const pod of pods) { - if (pod.metadata.name.startsWith(appName)) { + if (pod.metadata.name.startsWith(appName+'-kuberoapp')) { for (const container of pod.spec.containers) { this.emitLogs( pipelineName, diff --git a/server-refactored-v3/templates/buildpacks.yaml b/server-refactored-v3/templates/buildpacks.yaml new file mode 100644 index 00000000..f5b706cb --- /dev/null +++ b/server-refactored-v3/templates/buildpacks.yaml @@ -0,0 +1,139 @@ +--- +# Source: kuberobuild/templates/job-buikdpacks.yaml +apiVersion: batch/v1 +kind: Job +metadata: + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: buildpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + name: example-test-20240631-2237 +spec: + ttlSecondsAfterFinished: 31536000 + backoffLimit: 0 # 0 means do not retry + completionMode: NonIndexed + completions: 1 + manualSelector: false + parallelism: 1 + podReplacementPolicy: TerminatingOrFailed + suspend: false + template: + metadata: + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: buildpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + spec: + automountServiceAccountToken: true + securityContext: + fsGroup: 1000 + containers: + - name: deploy + env: + - name: REPOSITORY + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app + - name: TAG + value: "123456" + - name: APP + value: example + command: + - sh + - -c + - 'kubectl patch kuberoapps $APP --type=merge -p "{\"spec\":{\"image\":{\"repository\": + \"$REPOSITORY\",\"tag\": \"$TAG\"}}}"' + image: bitnami/kubectl:latest + imagePullPolicy: Always + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + initContainers: + - name: fetch + env: + - name: GIT_REPOSITORY + value: git@github.com:kubero-dev/template-nodeapp.git + - name: GIT_REF + value: main + - name: KUBERO_BUILDPACK_DEFAULT_RUN_CMD + value: "exit 0" + - name: KUBERO_BUILDPACK_DEFAULT_BUILD_CMD + value: "exit 0" + image: "ghcr.io/kubero-dev/fetch:latest" + imagePullPolicy: Always + resources: {} + securityContext: + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/kubero/.ssh-mounted + name: deployment-keys + readOnly: true + - mountPath: /app + name: app-storage + workingDir: /app + - command: + - sh + - -c + - chmod -R g+w /app + image: busybox:latest + imagePullPolicy: IfNotPresent + name: permissions + securityContext: + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /app + name: app-storage + workingDir: /app + - name: build + args: + - '-app=.' + - registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app:mytag-id + command: ['/cnb/lifecycle/creator'] + # https://github.com/buildpacks/pack/issues/564#issuecomment-943345649 + # https://github.com/buildpacks/spec/blob/platform/v0.13/platform.md#creator + #command: ['/cnb/lifecycle/creator', '-app=.', '-buildpacks=/cnb/buildpacks', '-platform=/platform', '-run-image=ghcr.io/kubero-dev/run:v1.4.0', '-uid=1000', '-gid=1000', 'kubero-local-dev-0037732.loca.lt/example/exampled:latest'] + #command: ['tail', '-f', '/dev/null'] + image: "paketobuildpacks/builder-jammy-full:latest" #List of Builders : https://paketo.io/docs/reference/builders-reference/ + imagePullPolicy: Always + resources: {} + env: + - name: CNB_PLATFORM_API + value: "0.13" + securityContext: + privileged: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + readOnly: false + - mountPath: /home/cnb/.docker + name: docker-config + readOnly: true + workingDir: /app + restartPolicy: Never + schedulerName: default-scheduler + serviceAccount: example-kuberoapp + serviceAccountName: example-kuberoapp + terminationGracePeriodSeconds: 30 + volumes: + - name: deployment-keys + secret: +# defaultMode: 420 + secretName: deployment-keys + - emptyDir: {} + name: app-storage + - name: docker-config + secret: + secretName: kubero-pull-secret + items: + - key: .dockerconfigjson + path: config.json +# - name: pull-secret +# secret: +# defaultMode: 0384 +# secretName: kubero-pull-secret \ No newline at end of file diff --git a/server-refactored-v3/templates/dockerfile.yaml b/server-refactored-v3/templates/dockerfile.yaml new file mode 100644 index 00000000..e0de68e0 --- /dev/null +++ b/server-refactored-v3/templates/dockerfile.yaml @@ -0,0 +1,124 @@ +--- +# Source: kuberobuild/templates/job-dockerfile.yaml +apiVersion: batch/v1 +kind: Job +metadata: + generation: 1 + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: dockerfile + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + name: example-test-20240631-2237 +spec: + ttlSecondsAfterFinished: 31536000 + backoffLimit: 0 # 0 means do not retry + completionMode: NonIndexed + completions: 1 + manualSelector: false + parallelism: 1 + podReplacementPolicy: TerminatingOrFailed + suspend: false + template: + metadata: + creationTimestamp: null + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: dockerfile + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + spec: + automountServiceAccountToken: true + securityContext: + fsGroup: 1000 + containers: + - env: + - name: REPOSITORY + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app + - name: TAG + value: 123456 + - name: APP + value: example + command: + - sh + - -c + - 'kubectl patch kuberoapps $APP --type=merge -p "{\"spec\":{\"image\":{\"repository\": + \"$REPOSITORY\",\"tag\": \"$TAG\"}}}"' + image: bitnami/kubectl:latest + imagePullPolicy: Always + name: deploy + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + initContainers: + - name: fetch + env: + - name: GIT_REPOSITORY + value: git@github.com:kubero-dev/template-nodeapp.git + - name: GIT_REF + value: main + - name: KUBERO_BUILDPACK_DEFAULT_RUN_CMD + value: "exit 0" + - name: KUBERO_BUILDPACK_DEFAULT_BUILD_CMD + value: "exit 0" + image: "ghcr.io/kubero-dev/fetch:latest" + imagePullPolicy: Always + resources: {} + securityContext: + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/kubero/.ssh-mounted + name: deployment-keys + readOnly: true + - mountPath: /app + name: app-storage + workingDir: /app + - name: push + command: + - sh + - -c + - |- + buildah build -f $BUILDAH_DOCKERFILE_PATH --isolation chroot -t $BUILD_IMAGE . + buildah push --tls-verify=false $BUILD_IMAGE + env: + - name: REGISTRY_AUTH_FILE + value: /etc/buildah/auth/.dockerconfigjson + - name: BUILD_IMAGE + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app:123456 + - name: BUILDAH_DOCKERFILE_PATH + value: /app/Dockerfile + image: "quay.io/containers/buildah:v1.35" + imagePullPolicy: IfNotPresent + resources: {} + securityContext: + privileged: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + readOnly: true + - mountPath: /etc/buildah/auth + name: pull-secret + readOnly: true + workingDir: /app + restartPolicy: Never + schedulerName: default-scheduler + serviceAccount: example-kuberoapp + serviceAccountName: example-kuberoapp + terminationGracePeriodSeconds: 30 + volumes: + - name: deployment-keys + secret: +# defaultMode: 420 + secretName: deployment-keys + - emptyDir: {} + name: app-storage + - name: pull-secret + secret: + defaultMode: 384 + secretName: kubero-pull-secret \ No newline at end of file diff --git a/server-refactored-v3/templates/nixpacks.yaml b/server-refactored-v3/templates/nixpacks.yaml new file mode 100644 index 00000000..ce2ae540 --- /dev/null +++ b/server-refactored-v3/templates/nixpacks.yaml @@ -0,0 +1,140 @@ +--- +# Source: kuberobuild/templates/job-nixpack.yaml +apiVersion: batch/v1 +kind: Job +metadata: + generation: 1 + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: nixpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + name: example-test-20240631-2237 +spec: + ttlSecondsAfterFinished: 31536000 + backoffLimit: 0 # 0 means do not retry + completionMode: NonIndexed + completions: 1 + manualSelector: false + parallelism: 1 + podReplacementPolicy: TerminatingOrFailed + suspend: false + template: + metadata: + creationTimestamp: null + labels: + batch.kubernetes.io/job-name: example-test-20240631-2237 + buildstrategy: nixpacks + kuberoapp: example + kuberopipeline: test + job-name: example-test-20240631-2237 + spec: + automountServiceAccountToken: true + securityContext: + fsGroup: 1000 + containers: + - env: + - name: REPOSITORY + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app + - name: TAG + value: 123456 + - name: APP + value: example + command: + - sh + - -c + - 'kubectl patch kuberoapps $APP --type=merge -p "{\"spec\":{\"image\":{\"repository\": + \"$REPOSITORY\",\"tag\": \"$TAG\"}}}"' + image: bitnami/kubectl:latest + imagePullPolicy: Always + name: deploy + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + initContainers: + - name: fetch + env: + - name: GIT_REPOSITORY + value: git@github.com:kubero-dev/template-nodeapp.git + - name: GIT_REF + value: main + - name: KUBERO_BUILDPACK_DEFAULT_RUN_CMD + value: "exit 0" + - name: KUBERO_BUILDPACK_DEFAULT_BUILD_CMD + value: "exit 0" + image: "ghcr.io/kubero-dev/fetch:latest" + imagePullPolicy: Always + resources: {} + securityContext: + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/kubero/.ssh-mounted + name: deployment-keys + readOnly: true + - mountPath: /app + name: app-storage + workingDir: /app + - name: build + command: + - sh + - -c + - nixpacks build . -o . + image: "ghcr.io/kubero-dev/build:latest" + imagePullPolicy: Always + resources: {} + securityContext: + privileged: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + workingDir: /app + - name: push + command: + - sh + - -c + - |- + buildah build -f $BUILDAH_DOCKERFILE_PATH --isolation chroot -t $BUILD_IMAGE . + buildah push --tls-verify=false $BUILD_IMAGE + env: + - name: REGISTRY_AUTH_FILE + value: /etc/buildah/auth/.dockerconfigjson + - name: BUILD_IMAGE + value: registry-kubero.yourdomain.com/optionalrepositoryowner/pipeline/app:123456 + - name: BUILDAH_DOCKERFILE_PATH + value: /app/Dockerfile + image: "quay.io/containers/buildah:v1.35" + imagePullPolicy: IfNotPresent + resources: {} + securityContext: + privileged: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app + name: app-storage + readOnly: true + - mountPath: /etc/buildah/auth + name: pull-secret + readOnly: true + workingDir: /app + restartPolicy: Never + schedulerName: default-scheduler + serviceAccount: example-kuberoapp + serviceAccountName: example-kuberoapp + terminationGracePeriodSeconds: 30 + volumes: + - name: deployment-keys + secret: +# defaultMode: 420 + secretName: deployment-keys + - emptyDir: {} + name: app-storage + - name: pull-secret + secret: + defaultMode: 384 + secretName: kubero-pull-secret \ No newline at end of file From 75f71dc5e0d8203e83336b5951ad9b805ffdda07 Mon Sep 17 00:00:00 2001 From: mms-gianni Date: Fri, 14 Feb 2025 14:30:46 +0100 Subject: [PATCH 46/65] Potential fix for code scanning alert no. 205: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../src/kubernetes/kubernetes.service.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/server-refactored-v3/src/kubernetes/kubernetes.service.ts b/server-refactored-v3/src/kubernetes/kubernetes.service.ts index 95eb24ad..2b05d3d5 100644 --- a/server-refactored-v3/src/kubernetes/kubernetes.service.ts +++ b/server-refactored-v3/src/kubernetes/kubernetes.service.ts @@ -1442,9 +1442,13 @@ export class KubernetesService { } private loadJob(jobname: string): V1Job { - const path = join(__dirname, `../../templates/${jobname}.yaml`) - this.logger.debug(`loading job from ${path}`) - const job = readFileSync( path, 'utf8') - return this.YAML.parse(job) as V1Job + const allowedJobNames = ['buildpacks', 'dockerfile', 'nixpacks', 'plain']; + if (!allowedJobNames.includes(jobname)) { + throw new Error(`Invalid job name: ${jobname}`); + } + const path = join(__dirname, `../../templates/${jobname}.yaml`); + this.logger.debug(`loading job from ${path}`); + const job = readFileSync(path, 'utf8'); + return this.YAML.parse(job) as V1Job; } } From d1b27fc71f3a36e0aca0a6f4abe378d650a4e69e Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sat, 15 Feb 2025 08:28:56 +0100 Subject: [PATCH 47/65] Migrate Metrics --- client/src/components/apps/alerts.vue | 2 +- client/src/components/apps/metrics.vue | 38 +++--- .../src/metrics/metrics.controller.ts | 114 +++++++++++++++++- .../src/metrics/metrics.service.ts | 7 +- .../src/settings/settings.service.ts | 5 + 5 files changed, 146 insertions(+), 20 deletions(-) diff --git a/client/src/components/apps/alerts.vue b/client/src/components/apps/alerts.vue index 3e08132e..82494f73 100644 --- a/client/src/components/apps/alerts.vue +++ b/client/src/components/apps/alerts.vue @@ -84,7 +84,7 @@ export default { }, 10000); }, loadRules() { - axios.get(`/api/rules/${this.pipeline}/${this.phase}/${this.app}`) + axios.get(`/api/metrics/rules/${this.pipeline}/${this.phase}/${this.app}`) .then(response => { this.rules = response.data }) diff --git a/client/src/components/apps/metrics.vue b/client/src/components/apps/metrics.vue index 53c1f814..39f7ac59 100644 --- a/client/src/components/apps/metrics.vue +++ b/client/src/components/apps/metrics.vue @@ -655,7 +655,7 @@ export default defineComponent({ }, getMemoryMetrics() { - axios.get(`/api/longtermmetrics/memory/${this.pipeline}/${this.phase}/${this.app}`, { + axios.get(`/api/metrics/timeseries/memory/${this.pipeline}/${this.phase}/${this.app}`, { params: { scale: this.scale } @@ -668,7 +668,7 @@ export default defineComponent({ }); }, getLoadMetrics() { - axios.get(`/api/longtermmetrics/load/${this.pipeline}/${this.phase}/${this.app}`, { + axios.get(`/api/metrics/timeseries/load/${this.pipeline}/${this.phase}/${this.app}`, { params: { scale: this.scale } @@ -681,9 +681,11 @@ export default defineComponent({ }); }, getHttpStatusCodeMetrics() { - axios.get(`/api/longtermmetrics/httpstatuscodes/${this.pipeline}/${this.phase}/${this.host}/rate`, { + axios.get(`/api/metrics/timeseries/httpstatuscodes/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -694,9 +696,11 @@ export default defineComponent({ }); }, getHttpStatusCodeIncreaseMetrics() { - axios.get(`/api/longtermmetrics/httpstatuscodes/${this.pipeline}/${this.phase}/${this.host}/increase`, { + axios.get(`/api/metrics/timeseries/httpstatuscodes/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -707,9 +711,11 @@ export default defineComponent({ }); }, getResponseTimeMetrics() { - axios.get(`/api/longtermmetrics/responsetime/${this.pipeline}/${this.phase}/${this.host}/increase`, { + axios.get(`/api/metrics/timeseries/responsetime/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -720,9 +726,11 @@ export default defineComponent({ }); }, getResponseTrafficMetrics() { - axios.get(`/api/longtermmetrics/traffic/${this.pipeline}/${this.phase}/${this.host}/increase`, { + axios.get(`/api/metrics/timeseries/traffic/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + host: this.host, + calc: 'rate' } }) .then((response) => { @@ -734,9 +742,10 @@ export default defineComponent({ }, getCpuMetrics() { // use 'rate' instead of 'increase' when comparing to limit and request - axios.get(`/api/longtermmetrics/cpu/${this.pipeline}/${this.phase}/${this.app}/increase`, { + axios.get(`/api/metrics/timeseries/cpu/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + calc: 'rate' } }) .then((response) => { @@ -748,9 +757,10 @@ export default defineComponent({ }, getCpuMetricsRate() { // use 'rate' instead of 'increase' when comparing to limit and request - axios.get(`/api/longtermmetrics/cpu/${this.pipeline}/${this.phase}/${this.app}/rate`, { + axios.get(`/api/metrics/timeseries/cpu/${this.pipeline}/${this.phase}/${this.app}`, { params: { - scale: this.scale + scale: this.scale, + calc: 'rate' } }) .then((response) => { diff --git a/server-refactored-v3/src/metrics/metrics.controller.ts b/server-refactored-v3/src/metrics/metrics.controller.ts index 369dc3b1..52420b19 100644 --- a/server-refactored-v3/src/metrics/metrics.controller.ts +++ b/server-refactored-v3/src/metrics/metrics.controller.ts @@ -1,5 +1,5 @@ -import { Controller, Get, Param } from '@nestjs/common'; -import { ApiOperation } from '@nestjs/swagger'; +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { ApiOperation, ApiParam } from '@nestjs/swagger'; import { MetricsService } from './metrics.service'; @Controller({ path: 'api/metrics', version: '1' }) @@ -7,6 +7,9 @@ export class MetricsController { constructor(private metricsService: MetricsService) {} @ApiOperation({ summary: 'Get metrics for a specific app' }) + @ApiParam({ name: 'pipeline', type: 'string' }) + @ApiParam({ name: 'phase', type: 'string' }) + @ApiParam({ name: 'app', type: 'string' }) @Get('/resources/:pipeline/:phase/:app') async getMetrics( @Param('pipeline') pipeline: string, @@ -17,6 +20,8 @@ export class MetricsController { } @ApiOperation({ summary: 'Get uptimes for pods on a Namespace' }) + @ApiParam({ name: 'pipeline', type: 'string' }) + @ApiParam({ name: 'phase', type: 'string' }) @Get('/uptimes/:pipeline/:phase') async getUptimes( @Param('pipeline') pipeline: string, @@ -24,4 +29,109 @@ export class MetricsController { ) { return this.metricsService.getUptimes(pipeline, phase); } + + @ApiOperation({ summary: 'Get timeseries' }) + @Get('/timeseries') + async getWideMetricsList( + ) { + return this.metricsService.getLongTermMetrics('up'); + } + + @ApiOperation({ summary: 'Get timeseries to draw metrics' }) + @ApiParam({ name: 'type', enum: ['memory', 'load', 'httpstatuscodes', 'responsetime', 'traffic', 'cpu'] }) + @ApiParam({ name: 'pipeline', type: 'string' }) + @ApiParam({ name: 'phase', type: 'string' }) + @ApiParam({ name: 'app', type: 'string' }) + @ApiParam({ name: 'scale', enum: ['24h', '2h', '7d'], required: false }) + @ApiParam({ name: 'calc', enum: ['rate', 'increase'], required: false }) + @ApiParam({ name: 'host', type: 'string', required: false }) + @Get('/timeseries/:type/:pipeline/:phase/:app') + async getWideMetrics( + @Param('type') type: "memory" | "load" | "httpstatuscodes" | "responsetime" | "traffic" | "cpu", + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + @Query('scale') scale: "24h" | "2h" | "7d", + @Query('calc') calc: "rate" | "increase" | undefined, + @Query('host') host: string, + ) { + let ret: any; + + switch (type) { + case 'memory': + ret = this.metricsService.getMemoryMetrics({ + scale: scale || '24h', + pipeline: pipeline, + phase: phase, + app: app + }); + break; + case 'load': + ret = this.metricsService.getLoadMetrics({ + scale: scale || '24h', + pipeline: pipeline, + phase: phase, + app: app + }); + break; + case 'httpstatuscodes': + ret = this.metricsService.getHttpStatusCodesMetrics({ + scale: scale || '24h', + pipeline: pipeline, + phase: phase, + host: host, + calc: calc + }); + break; + case 'responsetime': + ret = this.metricsService.getHttpResponseTimeMetrics({ + scale: scale || '24h', + pipeline: pipeline, + phase: phase, + host: host, + calc: calc + }); + break; + case 'traffic': + ret = this.metricsService.getHttpResponseTrafficMetrics({ + scale: scale || '24h', + pipeline: pipeline, + phase: phase, + host: host, + calc: calc + }); + break; + case 'cpu': + ret = this.metricsService.getCPUMetrics({ + scale: scale || '24h', + pipeline: pipeline, + phase: phase, + app: app, + calc: calc + }); + break; + default: + ret = 'Invalid type'; + break; + } + return ret; + } + + @ApiOperation({ summary: 'Get alerts Rules and status' }) + @ApiParam({ name: 'pipeline', type: 'string' }) + @ApiParam({ name: 'phase', type: 'string' }) + @ApiParam({ name: 'app', type: 'string' }) + @Get('/rules/:pipeline/:phase/:app') + async getRules( + @Param('pipeline') pipeline: string, + @Param('phase') phase: string, + @Param('app') app: string, + ) { + return this.metricsService.getRules({ + pipeline: pipeline, + phase: phase, + app: app + }); + } + } diff --git a/server-refactored-v3/src/metrics/metrics.service.ts b/server-refactored-v3/src/metrics/metrics.service.ts index 56d2d89a..d1ca1e24 100644 --- a/server-refactored-v3/src/metrics/metrics.service.ts +++ b/server-refactored-v3/src/metrics/metrics.service.ts @@ -25,7 +25,7 @@ export class MetricsService { //TODO: Migration -> Load options from settings or config const options = { enabled: true, - endpoint: 'http://prometheus.localhost', + endpoint: process.env.KUBERO_PROMETHEUS_ENDPOINT || 'http://kubero-prometheus-server', } as MetricsOptions; this.prom = new PrometheusDriver({ @@ -44,13 +44,14 @@ export class MetricsService { .status() .then((status) => { Logger.log( - '✅ Feature: Prometheus Metrics initialized::: ' + options.endpoint, + '✅ Feature: Prometheus Metrics initialized with ' + options.endpoint, 'Feature', ); this.status = true; }) .catch((error) => { - Logger.log('❌ Feature: Prometheus not accesible ...', 'Feature'); + Logger.log('❌ Feature: Prometheus not accesible on '+options.endpoint, 'Feature'); + Logger.debug(error); this.status = false; }); } diff --git a/server-refactored-v3/src/settings/settings.service.ts b/server-refactored-v3/src/settings/settings.service.ts index 84df1a84..b9b35325 100644 --- a/server-refactored-v3/src/settings/settings.service.ts +++ b/server-refactored-v3/src/settings/settings.service.ts @@ -278,6 +278,10 @@ export class SettingsService { return this.features.metrics; } + checkMetricsEnabled(): boolean { + return true; + } + private async checkForZeropod(): Promise { // This is a very basic check for Zeropod. It requires the namespace zeropod-system to be present. // But it does not check if the Zeropod controller is complete and running. @@ -299,6 +303,7 @@ export class SettingsService { private async runFeatureCheck() { this.features.sleep = await this.checkForZeropod(); + this.features.metrics = await this.checkMetricsEnabled(); } public getSleepEnabled(): boolean { From 6dfdcef2467f29cd306b33bb4a8cb45b16968547 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sat, 15 Feb 2025 23:01:33 +0100 Subject: [PATCH 48/65] Migrate auth page --- client/src/components/loginprompt.vue | 2 +- server-refactored-v3/src/auth/auth.controller.ts | 4 ++++ server-refactored-v3/src/auth/auth.service.ts | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/client/src/components/loginprompt.vue b/client/src/components/loginprompt.vue index 121e090d..74673ed1 100644 --- a/client/src/components/loginprompt.vue +++ b/client/src/components/loginprompt.vue @@ -120,7 +120,7 @@ export default defineComponent({ username: username, password: password } - axios.post("/api/login", data) + axios.post("/api/auth/login", data) .then((response) => { //console.log("Logged in"+response) router.push("/") diff --git a/server-refactored-v3/src/auth/auth.controller.ts b/server-refactored-v3/src/auth/auth.controller.ts index 5b63c490..7da5e916 100644 --- a/server-refactored-v3/src/auth/auth.controller.ts +++ b/server-refactored-v3/src/auth/auth.controller.ts @@ -12,6 +12,10 @@ import { AuthService } from './auth.service'; export class AuthController { constructor(private readonly authService: AuthService) {} + @Get('methods') + async getMethods() { + return this.authService.getMethods(); + } @Post('login') async login(@Request() req) { return req.user; diff --git a/server-refactored-v3/src/auth/auth.service.ts b/server-refactored-v3/src/auth/auth.service.ts index 9ccdf0a2..b70c256f 100644 --- a/server-refactored-v3/src/auth/auth.service.ts +++ b/server-refactored-v3/src/auth/auth.service.ts @@ -6,6 +6,13 @@ import { AuditService } from '../audit/audit.service'; @Injectable() export class AuthService { + + private methods = { + "local": true, + "github": true, + "oauth2": true + } + constructor( private usersService: UsersService, private kubectl: KubernetesService, @@ -50,4 +57,9 @@ export class AuthService { return { message: message, status: status }; } + + getMethods() { + return this.methods + } + } From 2568c76841d010c97519e39d0b915f7c3e46c373 Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Sun, 16 Feb 2025 23:18:03 +0100 Subject: [PATCH 49/65] migrate to JWT based authentication --- server-refactored-v3/package.json | 3 + server-refactored-v3/src/app.controller.ts | 6 + .../src/auth/auth.controller.ts | 9 +- server-refactored-v3/src/auth/auth.module.ts | 15 +- server-refactored-v3/src/auth/auth.service.ts | 13 ++ server-refactored-v3/src/auth/constants.ts | 3 + server-refactored-v3/src/auth/jwt.strategy.ts | 19 +++ .../src/auth/local.strategy.ts | 1 + server-refactored-v3/yarn.lock | 128 +++++++++++++++++- 9 files changed, 189 insertions(+), 8 deletions(-) create mode 100644 server-refactored-v3/src/auth/constants.ts create mode 100644 server-refactored-v3/src/auth/jwt.strategy.ts diff --git a/server-refactored-v3/package.json b/server-refactored-v3/package.json index 07044f07..76aafd6a 100644 --- a/server-refactored-v3/package.json +++ b/server-refactored-v3/package.json @@ -26,6 +26,7 @@ "@nerdvision/gitlab-js": "^1.0.0-alpha.12", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", + "@nestjs/jwt": "^11.0.0", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/platform-socket.io": "^11.0.7", @@ -44,6 +45,7 @@ "helmet": "^8.0.0", "passport": "^0.7.0", "passport-github2": "^0.1.12", + "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "passport-oauth2": "^1.8.0", "prometheus-query": "^3.4.1", @@ -65,6 +67,7 @@ "@types/jest": "^29.5.14", "@types/node": "^22.10.7", "@types/passport-github2": "^1.2.9", + "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/passport-oauth2": "^1.4.17", "@types/sshpk": "^1.17.4", diff --git a/server-refactored-v3/src/app.controller.ts b/server-refactored-v3/src/app.controller.ts index 46d62873..1ac2aed7 100644 --- a/server-refactored-v3/src/app.controller.ts +++ b/server-refactored-v3/src/app.controller.ts @@ -16,4 +16,10 @@ import { AuthGuard } from '@nestjs/passport'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} + + @UseGuards(AuthGuard('jwt')) + @Get('hallo/welt') + getHello(@Request() req): string { + return req.user; + } } diff --git a/server-refactored-v3/src/auth/auth.controller.ts b/server-refactored-v3/src/auth/auth.controller.ts index 7da5e916..2818e926 100644 --- a/server-refactored-v3/src/auth/auth.controller.ts +++ b/server-refactored-v3/src/auth/auth.controller.ts @@ -8,6 +8,7 @@ import { } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { AuthService } from './auth.service'; + @Controller({ path: 'api/auth', version: '1' }) export class AuthController { constructor(private readonly authService: AuthService) {} @@ -18,20 +19,20 @@ export class AuthController { } @Post('login') async login(@Request() req) { - return req.user; + return this.authService.login(req.body); } @Get('logout') @UseGuards(AuthGuard('local')) - async logout(@Request() req, @Response() res) { + async logout(@Request() req) { req.logout({}, function (err: Error) { if (err) { throw new Error('Logout failed: Function not implemented.'); } - res.send('Logged out'); + return { message: 'logged out', status: 200 }; } as any); console.log('logged out'); - return res.send('logged out'); + return { message: 'logged out', status: 200 }; } @Get('session') diff --git a/server-refactored-v3/src/auth/auth.module.ts b/server-refactored-v3/src/auth/auth.module.ts index 2e566263..c7e38c78 100644 --- a/server-refactored-v3/src/auth/auth.module.ts +++ b/server-refactored-v3/src/auth/auth.module.ts @@ -4,12 +4,23 @@ import { UsersModule } from '../users/users.module'; import { KubernetesModule } from 'src/kubernetes/kubernetes.module'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; +import { JwtStrategy } from './jwt.strategy'; import { AuthController } from './auth.controller'; import { AuditModule } from 'src/audit/audit.module'; +import { JwtModule } from '@nestjs/jwt'; +import { jwtConstants } from './constants'; @Module({ - imports: [UsersModule, PassportModule], - providers: [AuthService, LocalStrategy, KubernetesModule, AuditModule], + imports: [ + UsersModule, + PassportModule, + JwtModule.register({ + secret: jwtConstants.secret, + signOptions: { expiresIn: '3600s' }, + }), + ], + providers: [AuthService, LocalStrategy, JwtStrategy, KubernetesModule, AuditModule], controllers: [AuthController], + exports: [AuthService], }) export class AuthModule {} diff --git a/server-refactored-v3/src/auth/auth.service.ts b/server-refactored-v3/src/auth/auth.service.ts index b70c256f..32c4f6be 100644 --- a/server-refactored-v3/src/auth/auth.service.ts +++ b/server-refactored-v3/src/auth/auth.service.ts @@ -3,6 +3,7 @@ import { UsersService } from '../users/users.service'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { SettingsService } from '../settings/settings.service'; import { AuditService } from '../audit/audit.service'; +import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { @@ -13,11 +14,14 @@ export class AuthService { "oauth2": true } + private JWT_SECRET: string = 'CHANGEME'; + constructor( private usersService: UsersService, private kubectl: KubernetesService, private settingsService: SettingsService, private auditService: AuditService, + private jwtService: JwtService ) {} async validateUser(username: string, pass: string): Promise { @@ -29,6 +33,15 @@ export class AuthService { return null; } + async login(user: any){ //TODO: use a type for user + console.log("user: ", user) + const payload = { username: user.username, sub: user.userId }; + console.log("payload: ", payload) + return { + access_token: this.jwtService.sign(payload), + }; + } + getSession(req: Request): { message: any; status: number } { const isAuthenticated = false; const status = 200; diff --git a/server-refactored-v3/src/auth/constants.ts b/server-refactored-v3/src/auth/constants.ts new file mode 100644 index 00000000..f3795ced --- /dev/null +++ b/server-refactored-v3/src/auth/constants.ts @@ -0,0 +1,3 @@ +export const jwtConstants = { + secret: 'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.', +}; \ No newline at end of file diff --git a/server-refactored-v3/src/auth/jwt.strategy.ts b/server-refactored-v3/src/auth/jwt.strategy.ts new file mode 100644 index 00000000..24500b15 --- /dev/null +++ b/server-refactored-v3/src/auth/jwt.strategy.ts @@ -0,0 +1,19 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; +import { jwtConstants } from './constants'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: jwtConstants.secret, + }); + } + + async validate(payload: any) { + return { userId: payload.sub, username: payload.username }; + } +} \ No newline at end of file diff --git a/server-refactored-v3/src/auth/local.strategy.ts b/server-refactored-v3/src/auth/local.strategy.ts index 9bbed389..359df13b 100644 --- a/server-refactored-v3/src/auth/local.strategy.ts +++ b/server-refactored-v3/src/auth/local.strategy.ts @@ -10,6 +10,7 @@ export class LocalStrategy extends PassportStrategy(Strategy) { } async validate(username: string, password: string): Promise { + console.log("username: ", username) const user = await this.authService.validateUser(username, password); if (!user) { throw new UnauthorizedException(); diff --git a/server-refactored-v3/yarn.lock b/server-refactored-v3/yarn.lock index 42bb90e2..a9c45842 100644 --- a/server-refactored-v3/yarn.lock +++ b/server-refactored-v3/yarn.lock @@ -1068,6 +1068,14 @@ path-to-regexp "8.2.0" tslib "2.8.1" +"@nestjs/jwt@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-11.0.0.tgz#aef1590e70830c70fba0f59e9b17314dc4d36822" + integrity sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA== + dependencies: + "@types/jsonwebtoken" "9.0.7" + jsonwebtoken "9.0.2" + "@nestjs/mapped-types@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19" @@ -1620,6 +1628,21 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/jsonwebtoken@*": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.8.tgz#313490052801edfb031bb32b6bbd77cc9f230852" + integrity sha512-7fx54m60nLFUVYlxAB1xpe9CBWX2vSrk50Y6ogRJ1v5xxtba7qXTg5BgYDN5dq+yuQQ9HaVlHJyAAt1/mxryFg== + dependencies: + "@types/ms" "*" + "@types/node" "*" + +"@types/jsonwebtoken@9.0.7": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2" + integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg== + dependencies: + "@types/node" "*" + "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -1637,6 +1660,11 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node@*", "@types/node@>=10.0.0", "@types/node@^22.10.7": version "22.12.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.12.0.tgz#bf8af3b2af0837b5a62a368756ff2b705ae0048c" @@ -1665,6 +1693,14 @@ "@types/passport" "*" "@types/passport-oauth2" "*" +"@types/passport-jwt@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-4.0.1.tgz#080fbe934fb9f6954fb88ec4cdf4bb2cc7c4d435" + integrity sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ== + dependencies: + "@types/jsonwebtoken" "*" + "@types/passport-strategy" "*" + "@types/passport-local@^1.0.38": version "1.0.38" resolved "https://registry.yarnpkg.com/@types/passport-local/-/passport-local-1.0.38.tgz#8073758188645dde3515808999b1c218a6fe7141" @@ -2581,6 +2617,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -3196,6 +3237,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -5011,6 +5059,22 @@ jsonpath-plus@^10.2.0: "@jsep-plugin/regex" "^1.0.4" jsep "^1.4.0" +jsonwebtoken@9.0.2, jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -5021,6 +5085,23 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + keyv@^4.0.0, keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -5075,6 +5156,36 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -5085,6 +5196,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -5430,7 +5546,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.3: +ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5784,6 +5900,14 @@ passport-github2@^0.1.12: dependencies: passport-oauth2 "1.x.x" +passport-jwt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" + integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ== + dependencies: + jsonwebtoken "^9.0.0" + passport-strategy "^1.0.0" + passport-local@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" @@ -5802,7 +5926,7 @@ passport-oauth2@1.x.x, passport-oauth2@^1.8.0: uid2 "0.0.x" utils-merge "1.x.x" -passport-strategy@1.x.x: +passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== From 908ac4fb7bcd85ac21b3e94a05a0b07a55e2e5aa Mon Sep 17 00:00:00 2001 From: Gianni Carafa Date: Wed, 19 Feb 2025 21:30:55 +0100 Subject: [PATCH 50/65] Migrate to JWT Authentication --- client/src/components/loginprompt.vue | 10 +- client/src/components/templates/index.vue | 7 +- client/src/layouts/default/NavDrawer.vue | 17 +- client/src/layouts/default/View.vue | 11 +- client/src/plugins/index.ts | 6 +- server-refactored-v3/.env.template | 9 + server-refactored-v3/nest-cli.json | 3 +- server-refactored-v3/src/app.controller.ts | 6 - .../src/apps/apps.controller.ts | 18 +- server-refactored-v3/src/apps/apps.dto.ts | 245 ++++++++++++++++++ .../src/auth/auth.controller.ts | 76 +++++- server-refactored-v3/src/auth/auth.dto.ts | 50 ++++ server-refactored-v3/src/auth/auth.module.ts | 11 +- server-refactored-v3/src/auth/auth.service.ts | 42 +-- server-refactored-v3/src/auth/constants.ts | 3 - server-refactored-v3/src/auth/jwt.strategy.ts | 7 +- .../src/auth/local.strategy.ts | 20 -- server-refactored-v3/src/main.ts | 11 +- .../src/pipelines/pipelines.controller.ts | 2 +- server-refactored-v3/src/settings/env/vars.ts | 2 + .../src/{ => shared}/dto/ok.dto.ts | 0 21 files changed, 464 insertions(+), 92 deletions(-) create mode 100644 server-refactored-v3/src/apps/apps.dto.ts create mode 100644 server-refactored-v3/src/auth/auth.dto.ts delete mode 100644 server-refactored-v3/src/auth/constants.ts delete mode 100644 server-refactored-v3/src/auth/local.strategy.ts create mode 100644 server-refactored-v3/src/settings/env/vars.ts rename server-refactored-v3/src/{ => shared}/dto/ok.dto.ts (100%) diff --git a/client/src/components/loginprompt.vue b/client/src/components/loginprompt.vue index 74673ed1..078f00da 100644 --- a/client/src/components/loginprompt.vue +++ b/client/src/components/loginprompt.vue @@ -86,6 +86,9 @@ import router from "../router" import axios from "axios" import { defineComponent } from 'vue' +import { useCookies } from "vue3-cookies"; +const { cookies } = useCookies(); + export default defineComponent({ name: "Login", data: () => ({ @@ -123,7 +126,12 @@ export default defineComponent({ axios.post("/api/auth/login", data) .then((response) => { //console.log("Logged in"+response) - router.push("/") + + // Save topen token in local storage + //localStorage.setItem("kubero.JWT_TOKEN", response.data.access_token); + + const token = cookies.set("kubero.JWT_TOKEN", response.data.access_token); + window.location.href = "/" }) .catch((errors) => { this.error = true; diff --git a/client/src/components/templates/index.vue b/client/src/components/templates/index.vue index 0dceae2d..dd59a63e 100644 --- a/client/src/components/templates/index.vue +++ b/client/src/components/templates/index.vue @@ -215,6 +215,10 @@ type TemplatesList = { // }[] } +//Axios instance without Auth headers to load templates +const templates = axios.create(); +templates.defaults.headers.common = {}; + export default defineComponent({ sockets: { }, @@ -302,7 +306,8 @@ export default defineComponent({ }, async loadTemplates(indexUrl: string) { const self = this; - axios.get(indexUrl) + + templates.get(indexUrl) .then(response => { self.templatesList = response.data; forEach(self.templatesList.categories, (value, key) => { diff --git a/client/src/layouts/default/NavDrawer.vue b/client/src/layouts/default/NavDrawer.vue index 51e82aac..f2c85234 100644 --- a/client/src/layouts/default/NavDrawer.vue +++ b/client/src/layouts/default/NavDrawer.vue @@ -196,11 +196,14 @@ theme.global.name.value = localStorage.getItem("theme") || 'light';